Author Topic: Crazyness: C and C++ mixing  (Read 3479 times)

0 Members and 1 Guest are viewing this topic.

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Crazyness: C and C++ mixing
« on: June 09, 2021, 08:29:30 am »
Hello there.

I try to stick to esp-idf and play with lora rf module.
I know C.
But All the libraries i can find are made in C++.

Trying to mix C and C++ looks like a wild adventure and i dont want to spend time learning C++.

What are you doing in such situation ?
Im just an amateur hobbyist, im quite confused. Is it something usual in MCU world ?
 

Offline tszaboo

  • Super Contributor
  • ***
  • Posts: 5561
  • Country: nl
  • Current job: ATEX certified product design
Re: Crazyness: C and C++ mixing
« Reply #1 on: June 09, 2021, 08:33:51 am »
You can use all the features of C in C++.
You don't need to use the introduced features of C++.
Unless a library forces you to use them.
String manipulation is a lot easier in C++ so even if you just use those parts, you might be ahead.
Former username: NANDBlog
 

Offline sleemanj

  • Super Contributor
  • ***
  • Posts: 2733
  • Country: nz
  • Professional tightwad.
    • The electronics hobby components I sell.
Re: Crazyness: C and C++ mixing
« Reply #2 on: June 09, 2021, 08:39:12 am »
Take your .c file, rename it to .cpp, the end (for the most part).
~~~
EEVBlog Members - get yourself 10% discount off all my electronic components for sale just use the Buy Direct links and use Coupon Code "eevblog" during checkout.  Shipping from New Zealand, international orders welcome :-)
 

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #3 on: June 09, 2021, 09:14:30 am »
well is it very expensive to transform a c++ library into c ?
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1177
  • Country: se
Re: Crazyness: C and C++ mixing
« Reply #4 on: June 09, 2021, 10:02:09 am »
well is it very expensive to transform a c++ library into c ?
Short answer: it depends.

Slightly longer answer: it depends a lot on how the library is written.

Slightly long answer:
There are two aspects, in general, to a library: its interface and its implementation.
If the interface is class based, with ample use of inheritance, abstractions, or templates the task is very expensive.
A nice design in C++ (e.g. using a template to instantiate and use a specific GPIO port) would take a lot of effort to translate, and the result will probably look contrived and ugly.
The same can be said for the implementation, even though the low levels often tend to look more like C with classes, than real C++.

That said, many of the C++ library one finds on the net, especially Arduino crap, are C++INO (C++ in name only).

Take your .c file, rename it to .cpp, the end (for the most part).
But pay attention to all the 'minor' differences that will come back to bite your ass (e.g. const, enums etc...).
« Last Edit: June 09, 2021, 10:14:07 am by newbrain »
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #5 on: June 09, 2021, 10:46:46 am »
So what is the general strategy.
I had a little success to use a extern "C" adapting an exemple code provided with a library when i wanted to add usage (ssd1306) of another C library.
But so far, mixing those C and C++ is pretty new for me, and i spend too much time dealing with compilator issue.

Im more comfortable with Pure C. Therefor i should rewrite from scratch all i need i guess.
 

Offline newbrain

  • Super Contributor
  • ***
  • Posts: 1177
  • Country: se
Re: Crazyness: C and C++ mixing
« Reply #6 on: June 09, 2021, 12:22:25 pm »
...(ssd1306)...
Given how simple that device is, this is one case where I would write the library from scratch.
Mine (C for an OKDO E1 board using NXP SDK, written for FreeRTOS and using DMA) is 503 lines, including the usual 30% of blank lines and comments.
Nandemo wa shiranai wa yo, shitteru koto dake.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #7 on: June 09, 2021, 02:42:20 pm »
But All the libraries i can find are made in C++.
On the embedded device, they're actually freestanding C++, which – because of how much of it is actually implementation-defined and not defined by the C++ standard – can look much more like C than C++ (because freestanding C is well defined by the C standards).

While some core C++ features like classes and templates are available in freestanding C, many things are not.  The standard C++ library is not available, and neither are exceptions.  Operator overloading is rarely used (except for oddball numeric formats and mathematical vectors), and instead C-like explicit method calls are common.

I could make some suggestions on how to augment your C knowledge to exploit this environment to your fullest ability, but because it involves disregarding many things C++ enthusiasts insist are core C++ features that everybody should know, and I'm not interested in language lawyerism or advocacy, only getting shit done as efficiently and as robustly as possible in practice with long-term reliability and maintainability in mind, I shall stop here.
 

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #8 on: June 09, 2021, 03:02:02 pm »
Perhaps, a practical example is offered by Arduino with their use of a smart subset of the C++ for embedded stuff.
 

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #9 on: June 09, 2021, 03:09:25 pm »
Operator overloading is rarely used

Yes, over the years ... the only occasion I remember is when I used the {+, -, *, /} operators  to write a fixed-point library.

Why? fixed-point is not already supported in Gcc ... yes, nowadays it is, but I used a very old version of Gcc where fixed-point was not implemented.

So, I think my future needs for operator overload is even more rare nowadays  ;D
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #10 on: June 09, 2021, 05:39:57 pm »
Yep – although not all Arduino library code can be called "smart".

If one wants to see exactly how proven successful C++ freestanding code looks like, I suggest Paul Stoffregen's github repositories of Teensyduino cores and Arduino libraries as a starting point.

As an example, his MahonyAHRS implements a Mahony filter for IMUs.  The quaternion operations, quaternion to Euler angles, and even an inverse square root, are all written as C-like functions, while the library itself is pretty well written C++ otherwise: the implementation provides a nicely encapsulated state tracking object a developer-user can instantiate for each IMU they have, in case they have several IMU modules.

Because exceptions are not available (in any freestanding C++ environment I've used), you'll typically see errors reported in C fashion (error return values or error state tracking variables).  Unfortunately, a lot of Arduino code doesn't really check for errors, or even provide error codes...

One example of overloading in use I can think of is how the Teensyduino environment treats the built-in USB Serial object (all Teensies having native USB): the object itself evaluates to True if the USB Serial port is open in an application (on Linux, Mac, BSDs at least), and to False otherwise.  This way, it is trivial for an Arduino sketch running on a Teensy to know whether it is connected to an userspace program (or to a Serial Monitor) or not.
« Last Edit: June 09, 2021, 05:45:54 pm by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #11 on: June 09, 2021, 09:35:30 pm »
...(ssd1306)...
Given how simple that device is, this is one case where I would write the library from scratch.
Mine (C for an OKDO E1 board using NXP SDK, written for FreeRTOS and using DMA) is 503 lines, including the usual 30% of blank lines and comments.

Well there is a misunderstanding, it's the lorawan in C++ and ssd library in C. As far as i used an example from the lorawan library, in which i wanted to add oled printing, i had to include the C library into C++ code.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #12 on: June 10, 2021, 12:12:21 am »
(I am hesitant to post this, because I fear that this will lead to the perennial useless opinion-slinging about C and C++.
  That is not my intention; my intention is just to help extend knowledge about the embedded freestanding C++ environments currently available.  Those exclude most of the C++ standard, as freestanding C++ leaves almost everything up to the implementation; so, my suggestions should not be interpreted in any way as "how a C programmer should learn C++".  This is strictly about existing embedded C++ freestanding environments, and not about full C++ at all; about practical development in embedded environments, not about theory or standards.  If you were to ask me about how someone who already knows C should approach C++, my answer would necessarily be completely different.  My answer would also be very different if someone proficient in C++ were to ask me help with embedded development, although many core points would stay the same – the approach would differ.)

To start with, most existing libraries written for freestanding C++ like Arduino and similar frameworks rely on GNU toolchain compatibility.  That is, they expect that whatever the compiler used is, it should provide a similar freestanding C++ environment as GCC does.  Because of how the freestanding C++ is defined in the standard, the most common set of expectations and rules is the "GNU Freestanding C++ environment".  Now, others do exist, but that one is the "de facto standard" among the freely available libraries and code you'll find on the net.

First, you should read the publicly available final drafts of the C99 (n1256.pdf) (and preferably also C11 (n1570.pdf) and C17 (n2176.pdf)) standards to see how they define freestanding environments.  To simplify, the only standard includes available are <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>.

Then, assuming you accept GCC being the "de facto" standard here, you should look at what it provides for freestanding C.  In particular, many of the GCC C extensions are available; including integer overflow built-ins and a large number of other built-ins.  Essentially, the code for these is generated by the GCC compiler itself, using the features of the current hardware architecture, according to the compiler flags used.

At this point, you should realize we're very, very far from typical C programming environment.

For C++, the difference between a hosted environment (what most think of when they think or say C++) and a freestanding environment is even starker.
GCC, for example, requires linking against a special support library (libsupc++) to provide the kind of freestanding environment the C++ standard specifies.

This means that most C++ embedded libraries you see on the net are expected to work in an environment that is NOT a freestanding C++ environment as specified by the C++ standard; and probably best described as a C freestanding environment but compiled with a C++ compiler.

In fact, it is easier to describe what is "imported" from C++ into this environment:
  • Templates.
    These provide a way for the compiler to generate different implementations of the same code for different types used.  In C, the best analog is polymorphic include files, like I've shown an example of here.
    The "downside" of templates is that when used with lots of different data types, the code size (Flash or ROM used in embedded environments) can grow much more rapidly than one would otherwise expect.  In general, an extremely useful and powerful tool.
  • Namespaces.
    When using multiple libraries, or otherwise combining code, function and variable names often clash.  Namespaces are a simple but effective tool to manage this, and does not really affect generated code size at all.
    For a namespace tutorial, see here.  Simple to use, no real downsides.
  • Classes and interfaces.
    In embedded environments, classes are used to encapsulate functionality into easily instantated objects.  The class concept itself is the biggest difference to C, and requires a separate post to explore; or you could just learn for yourself for example starting at the cplusplus tutorial.  Interfaces are abstract classes, a description of what a class implementing the interface will provide; these are used so that different implementations can be interchanged without changing any of the code using them.
    In the Arduino world, a Print class implements data formatting, and all the various serial classes use it so they get print formatting for "free".  These are a powerful mechanism, and will make your life easier in this environment.
    You will need to learn how to use and create classes and interfaces; the biggest hurdle here is to understand the concepts correctly.
  • Operator overloading.
    C++ allows classes to define functions that handle operations, when objects of specific classes are used with that operator.
    These are most commonly used in embedded environments with oddball numeric types (say, a Q7.15 24-bit fixed point type), vectors (in the mathematical or geometric sense) and matrices.  Class objects are often overloaded for the bool() operator, so that if (instance) can be used to check if the particular instance is ready/available/okay or not.
    The kind of syntactic/abstract operator overloading that is common in C++, like cout << "stuff", is not used.
  • new and delete, if supported, are just syntactic sugar on top of malloc() and free().
  • References.
    See e.g. here and especially here.  Essentially, declaring a function parameter as type &name means that the user of the function can specify a variable of type type as that parameter, but the function receives a pointer to it.  Very commonly used, and therefore important to understand correctly.
(If I've missed something others expect to be available, please do point it out! I've almost certainly forgotten stuff from the above list.
This list is the "hot potato" I fear may start a flame war, because it may be seen as an assertion that "C++ is just C with classes", which is NOT true.  It's just that in this weird inbetween environment we're dealing with, they are the key concept imported from C++.)

The most notable common C++ features that are NOT used in embedded C++ environments:
  • Anything from the standard C++ libraries.  This includes everything from I/O to strings.  And this means most C++ tutorials really don't work for you, because you don't need to learn how "normal" C++ code does things; you need to understand the logical concepts underlying C++ and leverage them in this weird not-exactly-specific environment.
  • Standard Template Library, STL.  May become more widely supported when memory sizes increase.
  • Exceptions.  Implementing exceptions requires stack unwinding, which is really not feasible in memory-constrained devices.  Instead, errors are passed like in C, either as specific return values, or via error state variables (like errno in C).
    In the Arduino environment, unfortunately a lot of code assumes errors do not occur, so be careful.

I do claim that anyone familiar with or capable of writing freestanding C can learn the core parts of C++ to fully exploit the capabilities of this bastardized environment rather quickly.  The core point is recognizing that the end result is no longer C, and not really C++ either; but a completely new language that shares a lot with those, but still, uses its own way of looking at things and solving problems.  Understanding the concepts correctly, and internalizing how to best exploit them in different situations, is the key: you cannot "translate" code from one language to another – that's just the original language written using the new language; the point is to learn how to solve problems in this new/bastard/whatever language and environment we have here, abiding by its rather inane rules.  Which are not really standard, just.. sorta agglomerated together, like a snowball.

I would recommend reading some C++ tutorials, especially the cplusplus ones, so you understand the concepts listed above; and then just dive head first into the Arduino or other embedded libraries written in sorta-C++ for these embedded environments.  For example, the MCCI Arduino LoRaWAN library.  It looks like a BIG library with so many files, but most of those files contain just a couple of lines of code, so if you download or clone the repository, and start exploring the code in an IDE you find comfortable and can track functions over multiple files, you'll find there isn't that much "stuff" there at all (currently 7665 lines, of which about 2600 are whitespace and comments, and most of the rest are various structure and class definitions; with not much "functional code" at all – so just dive in!), and it is rather easy to understand –– after you grok how the original developers decided to look at things, that is.

I claim that anyone capable of writing the same library in freestanding C will learn (and internalize, so they can properly use) the abovementioned C++ concepts and then fully understand and even enhance the LoRaWAN library faster than they would write or rewrite the library in C.

The opposite direction, getting competent dedicated C++ programmers to become proficient in this oddball environment, is in my experience much harder, for the same reason single-OS power-users find it harder to switch tools than newbie users do: it is harder to "forget" and let go of tools and abstractions you rely on, than acquire completely new ones.  This obviously does not apply to C++ programmers who are also competent in other programming languages (with non-OO paradigms), because if they are, they've already learned how to switch "toolsets", and won't find it hard at all.  Individual exceptions do abound, obviously, because where there is will, there is a way, and when you're having fun, you're having fun.  So this should NOT be taken as a dig at C++ programmers; it is just an observation that C++ provides more tools and abstractions than C does, and in this particular case that can be a hindrance.  I've seen this in other situations wrt. other languages and OSes time and again, and think it is just an understandable quirk in humans, just something to know and work with when needed.  I do believe David2 was a bit bitten by this (based on the one or two videos he did make for EEVBlog2 channel), and I do believe he's definitely a competent C++ programmer.
 
The following users thanked this post: emece67, netmonk, Tagli, DiTBho

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 2309
  • Country: nz
  • Formerly SiFive, Samsung R&D
Re: Crazyness: C and C++ mixing
« Reply #13 on: June 10, 2021, 01:58:02 am »
(To simplify, the only standard includes available are <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>.

That is all that is guaranteed, but other libraries are permitted.

It would be a rare system that did not provide some variety of <stdlib.h> and <string.h>. Or replacements for them, such as Arduino does.

Of course it's quite trivial to code up any functions you need from those yourself, and then you are free to make any size / performance trade-offs you deem desirable in your environment e.g. slow but small byte-by-byte memcpy() might be fine instead of a half KB speed optimised version.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #14 on: June 10, 2021, 03:57:33 am »
It would be a rare system that did not provide some variety of <stdlib.h>
Well, no.

<stdlib.h> provides atof(), atoi(), atol(), atoll(), strtod(), strof(), strtolf() functions for converting strings to floating point formats; strtol(), strtoll(), strtoul(), strtoull() functions for converting strings to integer formats; rand() and srand() functions implementing (often poor) PRNG; calloc(), malloc(), realloc(), aligned_alloc() functions for dynamically allocating memory; free() for freeing dynamically allocated memory; abort(), exit(), _Exit(), quick_exit() for program termination; atexit(), at_quick_exit() for registering functions to be executed at program termination; getenv(), setenv(), putenv() for environment management; system() for executing commands using an external command processor; bsearch() and qsort() searching and sorting functions; abs(), labs(), llabs(), div(), ldiv(), lldiv() integer arithmetic functions; mblen(), mbtowc(), wctomb(), mbstowcs(), wcstombs() multibyte/wide character functions.

If you had said a subset of, then I'd happily fully agree, definitely: all reasonable ones provide malloc(), realloc(), free(), strtol(), strtoul(), atoi(), atol(), and GNU-compatible compilers provide abs(), labs(), llabs() via built-ins.

Some, but not all, provide div() and ldiv(). (These return both the quotient and the remainder.  They're rarely used, though.)

Implementations that have sizeof (long long) > sizeof (long), additionally tend to provide strtoll(), strtoull(), llabs(); and if they provide ldiv(), lldiv() too.

Implementations that have either hardware floating point or software-emulated floating point additionally provide atof(), strtof(), strtod(), and if sizeof (long double) > sizeof (double), strtold().

Some provide rand() and srand(), but poor versions.  Use a Xorshift* variant instead: they're almost always faster (no division, just XORs and bit shifts) and more random with longer periods than the linear-congruential PRNGs rand() and srand() traditionally implement.

None provide abort(), exit(), _Exit(), quick_exit(), atexit(), at_quick_exit(), getenv(), putenv(), setenv(), system(), because they just aren't applicable in embedded environments without an operating system.

I don't recall seeing any embedded environment using any of the multibyte/wide character functions, typically because of memory constraints, but Arduino's avr-libc and several arm cores do seem to have at least some sort of support.  (The Unicode library defines some 143,859 characters, so it's better to leave multibyte/wide character support to those who really need to implement it.)

I haven't checked on bsearch() and qsort(), but Arduino does provide both via avr-libc; and the arm cores I have installed in Arduino all use newlib for the arm equivalent of avr-libc, providing the same.

See?  Yes, parts of <stdlib.h> need to be available for example for memory allocation to be possible, but I've never seen a complete implementation in an embedded environment (as the ten functions related to process termination and environment variables just make no sense in a bare metal environment without an operating system).

I know perfectly well that brucehoult knows the above; I am not questioning his knowledge at all, he knows this stuff in-and-out.  I may look like I'm nitpicking, but I really just want to be very careful here, so that new programmers understand that whatever they read about C and C++ usually refers to the standard (hosted) environment, but this here, a mix of freestanding C and C++ but not really either (as the standards define them), is very far from that.

and <string.h>. Or replacements for them, such as Arduino does.
Most of <string.h>, yes.  AFAIK, Arduino relies on avr-libc to provide these.

GCC also provides most of them (strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr) via built-ins, like explained in the GCC built-in link I provided above.

While avr-libc (for AVRs) and newlib (for ARMs) do provide a more or less complete <string.h>, strcoll(), strxfrm(), strerror() are rarely used and may not be available.  (The others are so often used they get implemented sooner or later – although some may implement the BSD versions (see strlcpy()) or strncpy() with strlcpy() semantics.)

I believe it is better to start with the minimum set, so that one gets used to the idea of "standard" functionality not being available, and that when available, the implementation may be suspect.  (The avr-libc and newlib floating-point implementation being geared for correctness and being pretty darned slow is kinda-sorta important, when developing for non-hardfloat hardware.  It does not indicate things cannot be done with such hardware, and it does not indicate that software FP is always slow; it's just that these are geared for correctness and not for efficiency.  Sometimes, one needs either faster software FP emulation, possibly with tradeoffs (infinity/NaN and rounding mode details being perfect candidates) or a custom fixed-point math implementation, to do the job at hand.)

It is much easier and fun to discover the good (and familiar) parts in existing environments, and be suspicious enough of things like pre-packaged HALs to examine things, so that one can make an informed decision on what they'll trust and work with, and what needs to be (re-)implemented from scratch.  This is why I believe it is better to start with the assumption that things may not be available, and then discover that hey, in this particular one they are.  If you consider my rationale for this in my previous post, you see why I think this approach works better than the others.

Now, if brucehoult or others have found that other approaches (than the "minimal first" I'm pushing here) work better or have had good experiences with those, please do let me – us! – know.  I am often wrong, and even though this approach has shown the most promise and least nasty surprises when I've helped others learn, my experience is still limited, and I for one would be very interested to hear how others have helped navigate this.
 
The following users thanked this post: Ed.Kloonk, netmonk, DiTBho

Offline Feynman

  • Regular Contributor
  • *
  • Posts: 115
  • Country: ch
Re: Crazyness: C and C++ mixing
« Reply #15 on: June 13, 2021, 09:20:14 am »
You can use all the features of C in C++.
You don't need to use the introduced features of C++.
Unless a library forces you to use them.
String manipulation is a lot easier in C++ so even if you just use those parts, you might be ahead.
That's exactly the way I see it, too.

Since C is a subset of C++, you still can program C-style even if your compiler is set to C++. Yeah, yeah, ... I know that in some details C isn't a 100% subset of C++. But for the most part you can code the same way as you used to in C with cherry-picking some handy features like the mentioned string manipulation.
« Last Edit: June 13, 2021, 09:22:43 am by Feynman »
 

Offline AntiProtonBoy

  • Frequent Contributor
  • **
  • Posts: 927
  • Country: au
  • Trust me, I passed the Voight-Kampff test.
Re: Crazyness: C and C++ mixing
« Reply #16 on: June 14, 2021, 05:05:09 am »
IF you are programming in a C++ environment, then I fail to see why you wouldn't want to write C++ idiomatic code. Time and time again I see rookies making the same mistake of treating C++ like C.

Use strings, take advantage of RAII principles, encapsulate memory management with smart pointers, and so forth.
 
The following users thanked this post: nctnico

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #17 on: June 14, 2021, 05:32:34 am »
Which part of my lengthy "THIS IS NOT A TYPICAL C++ ENVIRONMENT" you missed, AntiProtonBoy?

:horse:

In particular, you'll be hard pressed to find C++ <string> – those string functions touted several times in this thread by members including yourself – in freestanding C++ environments.  A good example is Arduino, which implements its own bastardized String class.

:rant:
 

Offline AntiProtonBoy

  • Frequent Contributor
  • **
  • Posts: 927
  • Country: au
  • Trust me, I passed the Voight-Kampff test.
Re: Crazyness: C and C++ mixing
« Reply #18 on: June 14, 2021, 06:34:52 am »
To be honest, I skimmed over your posts, because it reads like a novel. That being said, strings aside, none of the above should preclude you from using built in C++ language features that takes advantage of RAII principles.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #19 on: June 14, 2021, 07:03:22 am »
That being said, strings aside, none of the above should preclude you from using built in C++ language features that takes advantage of RAII principles.
Except strings.  And exceptions.  And STL.  And the standard library.

Which you'd know if you had read the "novel".  But you, like so many others, choose to just throw advice you believe is correct and helpful, because actually considering and thinking about it and looking at what others have written would be too much effort.  The result is that anyone reading this thread will have hard time deciding who to believe; and because humans are gravitated towards easy answers, yours (and certain others) sound the easiest and therefore the ones others will believe.

Until, of course, they find out the hard way that that was bad advice.  Then, instead of remembering yours was just one of the opinions, they'll decide that everything else in this forum must be garbage, too; that because they listened to you and that backfired, everything else here is just as useless or even harmful.  And everybody loses.

Thank you for confirming I really should not have posted in this thread.  My mistake; I will learn from it.
 
The following users thanked this post: Siwastaja

Offline Unixon

  • Frequent Contributor
  • **
  • Posts: 351
Re: Crazyness: C and C++ mixing
« Reply #20 on: June 14, 2021, 07:13:05 am »
Trying to mix C and C++ looks like a wild adventure and i dont want to spend time learning C++.
It's not a wild adventure, it's completely normal.
You're putting yourself at a huge disadvantage by not learning C++, it makes life a lot easier [if not overused].
You don't have to learn C++ all at once, just pick features you need one by one.

What are you doing in such situation ?
Writing in C++ by default but using only as much language features as necessary so often it's just like C with a nicer syntax.
 

Offline AntiProtonBoy

  • Frequent Contributor
  • **
  • Posts: 927
  • Country: au
  • Trust me, I passed the Voight-Kampff test.
Re: Crazyness: C and C++ mixing
« Reply #21 on: June 14, 2021, 07:54:06 am »
Except strings.  And exceptions.  And STL.  And the standard library.

For starters, STL and built-in core language features are completely distinct concepts! STL is just a bunch of standardised libraries part of the C++ ISO standard. The language itself has a separate core specification. In other words, you can still write idiomatic C++ code without using STL. Professional game developers routinely drop STL in favour for custom libraries, and they also compile without exceptions and RTTI. Arduino is no different in that respect.

The point I'm trying to make here is this: rookies should not fall into the trap of treating C++ language as if it were "C with classes", and then create an absolute dog's breakfast in their project. This is in direct response to OP's dilemma about mixing C and C++ code. For more information, see Kate Gregory's excellent presentation on the subject.

Quote
Which you'd know if you had read the "novel".  But you, like so many others, choose to just throw advice you believe is correct and helpful, because actually considering and thinking about it and looking at what others have written would be too much effort.  The result is that anyone reading this thread will have hard time deciding who to believe; and because humans are gravitated towards easy answers, yours (and certain others) sound the easiest and therefore the ones others will believe.

Thank you for confirming I really should not have posted in this thread.  My mistake; I will learn from it.

Resentment will get you nowhere. Look, I don't mean to come across as unappreciative of your contribution. However, good communication is an important skill. When you express your thoughts in a very verbose manner, the actual message can be lost in a sea of redundant information. Trying to tease out the critical points in long text requires a lot of cognitive load - so of course humans will gravitate towards responses that are more succinct. People are constantly bombarded with excess amount of information every bloody day. Meanwhile, you are competing for their time and attention. In short: less is more; get to the point. If you can't do that... well, that's kinda on you?




 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: fi
Re: Crazyness: C and C++ mixing
« Reply #22 on: June 14, 2021, 08:51:44 am »
Resentment will get you nowhere. Look, I don't mean to come across as unappreciative of your contribution. However, good communication is an important skill. When you express your thoughts in a very verbose manner, the actual message can be lost in a sea of redundant information.

Except there was no redundant information and the actual message is not lost anywhere. IMHO, Nominal's posts tend to be almost miraculously good communication and always to the point, avoiding unimportant details and going logically deep into the important details that matter. It can be seen they are written by a person who not only has a logically working and quite intellectually honest brain, but puts a lot of effort to get the message through to the reader. But ultimately, the reader still has some responsibility. Again IMHO, if they fail to see the point, I don't think Nominal Animal could have done any better.

Also in my opinion, scientists and engineers should be able to cope with "walls of texts", many books on engineering subjects are over 1000 pages long and for good reasons. Sometimes you just can't distil it to a few lines, or maybe you can, but then you need to be trusted blindly, which seldom is a good idea.
 

Offline dunkemhigh

  • Super Contributor
  • ***
  • Posts: 3081
Re: Crazyness: C and C++ mixing
« Reply #23 on: June 14, 2021, 02:09:08 pm »
Quote
Except there was no redundant information and the actual message is not lost anywhere.

Indeed. If there's a lot of info to impart, it's going to be a long post, and there was little fat with plenty of meat. And I would suggest that absolute length isn't important - people will miss the second sentence of a two-sentence post if they think they have a quibble with the first one (mea culpa).
 

Online brucehoult

  • Super Contributor
  • ***
  • Posts: 2309
  • Country: nz
  • Formerly SiFive, Samsung R&D
Re: Crazyness: C and C++ mixing
« Reply #24 on: June 14, 2021, 03:11:14 pm »
Resentment will get you nowhere. Look, I don't mean to come across as unappreciative of your contribution. However, good communication is an important skill. When you express your thoughts in a very verbose manner, the actual message can be lost in a sea of redundant information.

Except there was no redundant information and the actual message is not lost anywhere. IMHO, Nominal's posts tend to be almost miraculously good communication and always to the point, avoiding unimportant details and going logically deep into the important details that matter. It can be seen they are written by a person who not only has a logically working and quite intellectually honest brain, but puts a lot of effort to get the message through to the reader. But ultimately, the reader still has some responsibility. Again IMHO, if they fail to see the point, I don't think Nominal Animal could have done any better.

I completely agree. Mr N Animal's posts are always informative and full of rigour, unlike mine. Always worth reading in full, and I do.
 
The following users thanked this post: Ed.Kloonk, newbrain

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: fi
Re: Crazyness: C and C++ mixing
« Reply #25 on: June 14, 2021, 03:36:11 pm »
What comes to RAII in microcontroller embedded, I don't think it's the best pattern, or any good at all.

Despite its name, the "strength" of RAII is not in initialization, but in deinitialization when the objects fall out of scope.

For initialization, it's just a bit of syntactic sugar of calling a constructor with (params) instead of init_object(&object, params) then error-checking through exceptions, instead of "if(!object)". Both are short and readable, just a bit different.

But the real argument for RAII in desktop is that you don't accidentally forget to deinit, and don't need to do it explicitly, it happens automatically when the object goes out of scope. Great, I agree!

But on MCUs, the #1 pattern is to never deinitialize at all! You allocate mostly fixed resources as a very first thing in main(), even on quite complex projects. And further, when you do need to deinitialize, that is always for some very good reason, and it's not going to be a case of "oh, let the compiler do it somewhere implicitly, I don't care"; no, it's going to be something where the exact timing is likely going to matter, it might intervene with other peripherals in twisted ways, and you want to handle the whole deinit/reinit/whatever shebang on the lowest possible level, very likely having to break "nice abstractions".

High level properly designed C++ projects are amazing cathedrals, and yes they do exist, but in real life it's not always that easy, especially on a small MCU with peripherals that connect to each other by design.
« Last Edit: June 14, 2021, 03:41:44 pm by Siwastaja »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7165
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #26 on: June 14, 2021, 04:48:37 pm »
Just to add a quick note about the original question, which was mixing C and C++ in the same project.

I have done this in the past - never in embedded projects, but on some "desktop" projects, for which I had to use a library written in one language within a project mainly written in another. So that's pretty much what the OP describes. First step when deciding to do this is have a good rationale for choosing one particular language, and for choosing one particular library written in another. When things are clear this is a good idea (or maybe sometimes the only option), then you can proceed.

First, define and implement an interface - that may be a thin layer over the original interface. Of course, if you're using a C library in a C++ project, you may not need to define an extra interface at all - C++ can use C functions provided they are declared as extern "C". Now if this is the opposite (using a C++ lib in a C project, something I've done a couple times too), obviously you'll need to add a bridge interface. This will be written in C++ (to be able to use the C++ library) and will expose C functions (and possibly data structures) that can be called from C.

Compiling C as C++ is rarely a good idea and actually yields no benefit, except for not necessarily having to care with 'extern "C"', but really, that's something basic to learn. C is definitely NOT a subset of C++. Many rules are different, and this is a nice recipe for headaches and posting a lot of annoying questions on dev forums. Sure if you're writing C code *yourself* that you want to be compilable as C++, it's doable. But you'll need to know what is not C about that. Now if this is some third-party C code, chances are, it will require a lot of modifications for even be compilable. Rarely worth it.

So if you have a C lib that you want to use within C++, just compile it as C, and make sure its interface (through some header file) are enclosed with 'extern "C"'. That's all there is to it.

This can apply to any other mix of languages. Some complex projects can be made of pieces written in different programming languages, this can actually make sense. So in general, each will be compiled with its own compiler, an interface between languages will be required, and that's it. In particular, many languages can call C stuff, even ADA!

Just my 2 cents.
 
The following users thanked this post: netmonk

Offline Unixon

  • Frequent Contributor
  • **
  • Posts: 351
Re: Crazyness: C and C++ mixing
« Reply #27 on: June 14, 2021, 06:22:03 pm »
Thinking of C and C++ as two different languages seems wrong, it is one language that comes in full featured and reduced modes.
Please correct me here, but I tend to think that the only reason to choose "pure C" is when a compiler for full C++ mode is not available for a particular hardware architecture.
 

Offline Siwastaja

  • Super Contributor
  • ***
  • Posts: 4199
  • Country: fi
Re: Crazyness: C and C++ mixing
« Reply #28 on: June 14, 2021, 06:32:50 pm »
Thinking of C and C++ as two different languages seems wrong, it is one language that comes in full featured and reduced modes.

No, no and no. It makes no sense to spell it out to you specifically, you'll find a lot of educational material and discussion about this using the popular search engines, but in a nutshell, C is not a subset of C++, the seemingly common parts have many more or less subtle differences, you notice it right away when you try to compile any large/complex C project using a C++ compiler (in C++ mode).

They are both properly standardized and specified languages developed by different steering groups resulting in different standards.

And obviously, born almost two decades earlier, C is completely its own language and guess what, there are C compilers in existence, which are not "C++ compilers working in reduced mode".

Also there are many good reasons to choose "pure C" over some subset of C++, one of which is actually to keep harmful programmers out of the project.
« Last Edit: June 14, 2021, 06:40:24 pm by Siwastaja »
 
The following users thanked this post: newbrain

Offline evb149

  • Super Contributor
  • ***
  • Posts: 1896
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #29 on: June 14, 2021, 06:50:48 pm »
In spirit you're qualitatively correct.
In practice, as has been said below, there are quite a few differences between what is supported by C++ and C even in areas where they
could easily overlap but don't (yet anyway).

Many of the shortcomings of C / C++ and many of the "it makes no sense to be different" differences between current C++ and current C are presumably mainly due to the hugely inertial process of bureaucratic standards committee related process that has to be gone through to make ANY change in the formal definition of the languages.  Language lawyers  argue over the tiniest syntactic and semantic differences and the "perceived usefulness" of any change for months or years before they form consensus to vet a particular proposal to even get it to the point where it'll be voted on.

There are a lot of obvious improvements to C / C++ which aren't in there probably just because nobody has put in the months / years effort to champion the change and get it approved.

Other areas where C hasn't caught up to C++ might get standardized in a way that homogenizes the languages moreso, but new standards only come out once every three years or so, and there's a long list of things that get either rejected for no technical reason or simply deferred for the future years.

Then even when the standard is updated it may take a year for the major new libraries / tool-chains to mostly catch up and a few years after that before those versions become "mainstream" for deeply embedded system use like for your typical microcontroller project.

In some ways I admire the greater agility / freshness of some of these newer languages that have more "shiny new" improvements more often.

It wouldn't take much to displace both C and C++ from a lot of areas where they're traditionally dominant.  On the one hand Moore's law has made even "low cost" MCUs capable of running a fairly sanely comprehensive C++ / STL based program without taking too much RAM / FLASH.  On the other hand more languages are getting more friendly "embeddable" subsets of their features / libraries into a performance / size range that could compete with C++.

Anyway in practice the C++ / C subset differences aren't THAT big of a deal.  If one programs based upon strict adherence to the language in question, most compilers / linters can do a pretty good job looking for cases of implementation defined, undefined, or non-standard behavior / usage and trigger a build error based upon that.  Then it's pretty trivial to ameliorate the lint by reformulating a few simple constructs to please the compiler where something isn't supported / permitted.


Thinking of C and C++ as two different languages seems wrong, it is one language that comes in full featured and reduced modes.
Please correct me here, but I tend to think that the only reason to choose "pure C" is when a compiler for full C++ mode is not available for a particular hardware architecture.
 

Offline Unixon

  • Frequent Contributor
  • **
  • Posts: 351
Re: Crazyness: C and C++ mixing
« Reply #30 on: June 14, 2021, 07:11:43 pm »
and guess what, there are C compilers in existence, which are not "C++ compilers working in reduced mode".
Sure I am aware of that. This is exactly my argument about reason behind choice of using C instead of C++.

p.s. After re-reading C/C++ list of incompatibilities I would say that where those take place C allows for more bad coding practices.
Here I'm referring to Wiki [https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B].
 

Offline AntiProtonBoy

  • Frequent Contributor
  • **
  • Posts: 927
  • Country: au
  • Trust me, I passed the Voight-Kampff test.
Re: Crazyness: C and C++ mixing
« Reply #31 on: June 15, 2021, 03:13:55 am »
What comes to RAII in microcontroller embedded, I don't think it's the best pattern, or any good at all.
If the language offers the feature, when why not use it as needed? Every time you need to manage state and there is a requirement to clean up, RAII is the best pattern for the job. The resource in question could be literally anything ranging from memory, to IO ports that controls an external device, or whatever.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #32 on: June 15, 2021, 09:35:20 am »
Just to add a quick note about the original question, which was mixing C and C++ in the same project.
No, the OP just didn't word it properly.  Everything they mentioned was specifically not standard C nor C++, but the kinds of freestanding/oddball libraries using subsets of C++ and/or C in embedded environments.

Yes, extern "C" { ... } pattern is relatively common in these environments, simply because they do mix both C and C++.  Typically the main reason is not so much language compatibility, but link-time and symbol compatibility, as GCC does not "mangle" C symbols like it does C++ symbols.  Even Arduino core <Arduino.h> includes that.

RAII does not apply well to embedded environments with limited resources, because the vast majority of objects are always present (on ARMs and AVRs, often initialized by the bootloader, by copying initial data structures from ROM/Flash, so that when code execution starts, all static initialization has already been done).

To ensure the order of objects requiring dynamic initialization (probing etc., say enumerating sensors on an I2C bus) across compilation units, GCC/G++ init_priority attribute is sometimes needed to ensure the initialization order required by hardware, when those aren't trivially expressed by the C++ code.  So, typical C++ objects used in this environment are used in a very different manner/pattern than what you see in typical application or system-level C++ code: vast majority of nontrivial objects in this environment are declared statically in file scope, because anything else would be a waste of resources.

Besides, the raison d'être for the RAII approach is exception safe resource management – and as I've described, exceptions are not supported by most of these environments at all.  It is just not the correct tool for the job here.

Simply put, neither the typical C++, or C, development paradigms are directly suitable for this not-very-standard environment.  Parts and aspects, yes, definitely; this isn't that different.  But the differences are big enough to trip people.

I have not described any specific paradigms that are suitable here, because they are heavily influenced by the base libraries available, as well as the regulatory domain one wants to conform to (MISRA aka Motor Industry Software Reliability Association in particular).  In short, there isn't one; there are many.  And that, too, means that it is best to start from minimal expectations, and build up from there.

Personally, I do not even have "one": I have a set, like a toolbag, from which I select the ones that seem to fit the problem best, and am continuously learning new ones.  I will never, ever know what the "best" one is, because I never use just one in isolation; and for each use case, the set is different.  Even the license for the work product is a tool.  This approach seems to work quite well.
 
The following users thanked this post: Fredderic

Offline Tagli

  • Contributor
  • Posts: 27
  • Country: tr
Re: Crazyness: C and C++ mixing
« Reply #33 on: June 15, 2021, 10:22:28 pm »
RAII becomes irrelevant in these 2 cases:

1. Dynamic allocation isn't used in the project.
2. Dynamic allocation is used, but during initialization only and the objects never get destroyed.

These both cases are common in embedded systems with constrained resources.

I think dynamic allocation helps to organize object initialization order, and makes it easier to write generalized & easy to configure classes. In my projects, I generally use "allocate during initialization only and never destroy anything" method.

Placement New is another good C++ tool that allows object initialization on statically allocated memory buffers. It can help avoid using heap.
Gokce Taglioglu
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3484
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #34 on: June 16, 2021, 12:30:06 am »
Quote
see Kate Gregory's excellent presentation on the subject.
Watched it (again, actually.)  While her points make a lot of sense for desktop application developers, you'll note that she pretty much IMMEDIATELY recomends using exactly the features that Animal mentions are frequently not present in "freestanding C++ environments.""Things will be much simpler and understandable if you use C++ <String> instead of "char*" arrays, and <vector> instead of C arrays (and the rest of <algorithm> instead of re-inventing wheels.)  Also, avoid using pointers."

That's swell in a traditional C++ environment, but fails pretty immediately in most microcontroller environments.  Not only is the STL frequently "not included", but the usual implementations are full of features (like dynamic allocation) that are actively avoided in small embedded environments.  (For example, Arduino has its own simplified implementation of <String>, but it's widely suspected of being broken for "typical" usage, because of the way it thrashes memory.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #35 on: June 16, 2021, 06:59:13 am »
You hit the nail exactly, westfw.

This is not about "normal" C or C++ or their relative merits.  This is about a funky environment that works best with a specific subset of both, both in the language spec, and in the approach.  Plus quite a few utterly nonstandard GNU and ELF tricks, because they're the most widely supported toolchain (with those tricks supported by many non-GNU toolchains also).

As an analog:

Many people know that when dealing with aluminium, especially cast aluminium, you can actually use woodworking tools to work with it.  They don't work perfectly, and dedicated aluminium tools will perform better and leave a nicer finish; but you can do it in a pinch without damaging your woodworking tools if you do it carefully (not forcing the tool, letting it do its job at its pace; not letting the tools bind or jam, heat up, etc).

My approach here is to AVOID starting with that, and instead examine the properties of aluminium and see how it can be machined and worked with, and what kind of tools work best for it; what to avoid, what to do in a pinch, and what some of the hard-won practical tips and tricks are.

I believe, and claim, that starting by comparing to woodworking or working with steel and hard metals, is problematic, because it does not "build" new knowledge; it relies on existing knowledge being correct, and teaching by comparison.  Instead, I want learners to start at the very basics, so they can expand their understanding, tie it to whatever they already know – and optionally fix/expand their knowledge elsewhere, if they discover they learned/believe something incorrectly – without having to start with their existing knowledge and habits and change those to apply to this situation.

It makes a lot of sense, even if it offends those who insist on comparing C and C++ merits, and using a singular tool for everything ignoring the task at hand.


Example: ELF-assisted automatically-collected arrays

Since most embedded toolchains use the ELF file format for object file representation, we can use the ELF file format properties, and the linker, to do useful work at compile time.  This is most commonly used to collect variables and objects declared at random places into a single, contiguous array of memory; either RAM or ROM/Flash.
The variables or objects only need to be declared in the file scope, but can be static (their name/symbol not exported outside the compilation unit or scope), with a custom section attribute, using syntax __attribute__((section ("name"))).

For full control of how sections are mapped to the final binary or target address space, a linker file is used.  However, most/all default to a linker file that has catch-all rules based on prefixes; for example, that ".rodata.foo" is merged with read-only data, ".rodata", and so on; the linker even provides symbols whose address corresponds to the beginning and end of these sections.  So, for simple cases, like collecting structures or objects that define a supported command the embedded device provides into a single consecutive array with known size, one only needs to add the section attributes, declare the section start and end "variables" as externs, and that's it; the linker will do the work for you, even if the structures and objects are defined in a number of different compilation units (separate object files).

The only "trick" here is that each of the structures/objects/variables thus collected needs to have a specific size; and this is affected by packing and padding rules.  Either the size must be the same for all objects and match that of an array element of that object type, in which case it can be treated as a normal array; or the exact size must be at the beginning of the object, so that the "array" can be traversed like a list.

For base type objects (data pointers, for example), or objects of the same type with a suitable size (end padding is often a tricky problem), you don't need to bother, and just keeping the structures as C++ will work absolutely fine.  (This is exactly how GCC implements static initializer and finalizer functions: their addresses are collected in .init_array and .fini_array sections, which the library start and exit code uses to call those functions without parameters.)

AIUI, C rules differ, so you may need to use extern "C" { ... } and declare them as C structures, with members in specific order and explicit padding members (making each N-bit member aligned to N-bit boundary, with largest members first), to get this to be portable across different hardware architectures, even across 8-bit/32-bit ones.  I would also use the C rules for objects of varying size, with the size as the first member in each object.

Usually, a bit of preprocessor macro magic is used, so that all the source code shows is something like
    EXPORT_COMMAND("foo", command_processing_function);
in the file scope of a module or source file implementing a specific command or command set.  The command_processing_function does not even need to be exported; it is sufficient for the symbol to be visible at that point in the file scope.

I hope you see what this can mean for typical command-processing firmware implementation; how much cleaner and simpler it can make both the source organization and the build machinery.  Yet, it is rarely used, because it is not something you use or teach others about in standard hosted environments, because of non-standardness and limited portability there.  Here, the situation is different.  It is perfectly suitable for the approach/paradigm in this environment.

(This is also exactly how the Linux kernel implements kernel module information including licenses and module options/parameters: it uses the linker to do the work.  That's where I initially learned about it.  I have used it in systems programming on ELF-based architectures, too; it works fine in userspace in Linux, Mac OS, BSDs, etc.)
« Last Edit: June 16, 2021, 07:04:53 am by Nominal Animal »
 

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #36 on: June 16, 2021, 11:14:53 am »
Well, Im more familiar with C than with C++ idiom. (i come from ASM and TP)
My issue was that i need a lorawan library written in C++ to use into a C project.
As far as there isn't C library i can use on ESP32 with esp-idf.

For little testing i was able to use exemple.cpp provided by the lorawan library, to send a packet successfully to TTN. But basically i have already working code of i2c temp sensor in C and im basically more familiar with C so i was asking what would be the strategy ?
I just dont want to learn bloated C++ or whatever you call it, i was always since 25 years allergic to OOP in any kind of way.(it started at univ when my partner was dealing with all the java work while i was dealing with all the C work)

I remember one of the HFT dev i was working with few years ago, after interviewing candidates :"that's horrible, they cannot do an *hello world* without libboost".
May be i'm ultra biaised, may be i'm stupid, but i really allergic of C++ and seeing all those c++ library for no reasons i cannot use is frustrating me :)
« Last Edit: June 16, 2021, 11:26:29 am by netmonk »
 

Offline Unixon

  • Frequent Contributor
  • **
  • Posts: 351
Re: Crazyness: C and C++ mixing
« Reply #37 on: June 16, 2021, 04:17:52 pm »
My issue was that i need a lorawan library written in C++ to use into a C project.
Which library exactly?

what would be the strategy ?
That depends on what you want from that library and what it provides and how.
Basically, you either write a C wrapper for it or cure C++ allergy.

all those c++ library for no reasons i cannot use is frustrating me :)
Well, maybe there are reasons... including that it could be more comfortable for people to write that way.

I agree that latest standards changed old good C++ beyond recognition and now it's not a rare thing to see a code in formally correct C++ without having a slightest idea what it does and how.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #38 on: June 16, 2021, 04:18:45 pm »
I just dont want to learn bloated C++ or whatever you call it
Yet, the parts of C++ that you need in this domain – classes, templates, namespaces – would make development easier for you.

In this domain, classes are typically used for encapsulation.  Consider a microcontroller with seven UARTs, and you want to implement a command to format and send string type data via any of them.  In C, you need to pass a parameter identifying the UART; in C++, if you call via the object, the reference is implicit.
In other words, your C interface might look like
    send_string(uart, "format", ...)
and your C++ interface like
    uart.send_string("format", ...)

Within a class, you can access the class members as if they were in local scope, without any kind of prefix or pointer notation.  Each member can be public, private, or protected (which is kinda-sorta public to classes derived from this one, and private to all others).

Instead of duplicating functionality in different classes, you can create a class (or an abstract class aka interface) that implements the common functionality, and have the other classes inherit the functionality.  This is how e.g. Arduino implements just one Print class for its print() and println() functions, but lets users use it with UARTs, USB, et cetera.

Function overloading is easier in C++ than it is in C.  You can have multiple functions with different signatures, and the compiler will call the one with compatible parameters.  In C, you need to use the C11 _Generic selection in a preprocessor macro named as the "generic" function, choosing the actual function based on parameter type(s).  This way, if you use const char and const unsigned char for immutable strings in ROM/Flash, and non-const for those in RAM, you can overload your send_string function so that
    send_string(uart, "string literal")
    send_string(uart, message)
and
    uart.send_string("string literal")
    uart.send_string(message)
will Do The Right Thing for both string literals in Flash/ROM, as well as non-const char array message in RAM.  For C, it does require preprocessor macro trickery; C++ does it "natively".

Remember, right now, Arduino completely messes this up, requiring developers to use F() macros and _P() -suffix functions to handle these correctly.  Yuk.

Namespaces are more of a visual/brevity thing.  Instead of having to write uart_send_string(), you can enclose a set of functions, objects, variables etc. inside a namespace, where they can be accessed using short names that are also used in other namespaces.  You can use using namespace namespace; to set the "default" namespace, import individual names from other namespaces via using namespace::name; , and refer to a specific name in a specific namespace using namespace::name.  This means you don't need to prefix each globally visible name with the module/library/feature prefix, you can let the compiler handle that detail.

i'm really allergic to C++
Like I keep saying, this is not "real" C++, this really is just a small subset of C++ (plus freestanding C), and should be considered its own language.

I'm allergic to Perl, myself; but even so, I very happily use regular expressions, even the Perl variants. I will happily use the Linux kernel checkpatch.pl, even examine it if need be (although reluctantly); as long as I don't have to fix others' bugs in it.

Before you reject this subset of C++, make sure you know what you're rejecting, and are not just assuming it is the same stuff.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7165
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #39 on: June 16, 2021, 04:57:20 pm »
Just to add a quick note about the original question, which was mixing C and C++ in the same project.
No, the OP just didn't word it properly.  Everything they mentioned was specifically not standard C nor C++, but the kinds of freestanding/oddball libraries using subsets of C++ and/or C in embedded environments.

The OP didn't mention much actually. I think you read a lot from what they were asking. A bit too much.

As their last post says, they exactly wanted to do this: use a C++ library in a C project. Your points were all interesting and a great read, but I still think my straight answer was more to the point.

If they insist on using a C++ third party library and call it from C, they'll need to write an interface. That's not rocket science. And as I mentioned in my previous post, whether in the end this is a good idea or not depends on a number of factors.

I haven't looked at the library the OP mentions specifically. First step would be of course to try and find a pure C library implementing the same. Now if there isn't any, or if the OP is convinced this one is very good and worth using, then two options: use C++ for their whole project, or write an interface for accessing the lib from C. It's perfectly doable, has been done a lot and there's nothing inherently wrong with that. Of course yet another option would be to re-implement the C++ library in pure C. If said library is not overly big, this is probably not a lot of work. But this would of course require the OP to know C++ well enough to do this; which is probably not the case here.

But all in all, selecting a language JUST because you want to use some specific library sounds like a very bad idea to me. So if the OP meant to write their project in C, suddenly switching to C++ just to be able to use some lib is fucked up. I mean, if they don't have any other solid rationale. This would be a recipe for a lot of frustration, probably bugs, and this would be promoting once again the use of an poorly (read: not) defined subset of C++, which is a real plague IMHO. But that point was discussed in a number of other threads...
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #40 on: June 16, 2021, 05:25:43 pm »
The OP didn't mention much actually. I think you read a lot from what they were asking. A bit too much.
They did.  ESP-IDF and LoRa RF are explicitly embedded/IoT stuff.  They also specifically stated "Is it something usual in MCU world?"

Feel free to disagree, but in my opinion, the context of the question is clear.  And it is not what one might think by reading only the subject title, at all.
« Last Edit: June 16, 2021, 05:27:55 pm by Nominal Animal »
 

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #41 on: June 16, 2021, 09:17:27 pm »
This library https://github.com/manuelbl/ttn-esp32
I even opened an issue requesting full C translation : https://github.com/manuelbl/ttn-esp32/issues/38
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3484
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #42 on: June 16, 2021, 09:56:17 pm »
Quote
if you use const char and const unsigned char for immutable strings in ROM/Flash
  :
Arduino completely messes this up, requiring developers to use F() macros and _P() -suffix functions to handle these correctly.
I don't think this is Arduino's fault.  At the C and even architectural level, "const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR, without breaking or bloating all of the standard C library functions, not to mention users' code.  (and the _P functions from avr-libc, not from Arduino...)
 

Offline Unixon

  • Frequent Contributor
  • **
  • Posts: 351
Re: Crazyness: C and C++ mixing
« Reply #43 on: June 17, 2021, 07:42:28 am »
This library https://github.com/manuelbl/ttn-esp32
I even opened an issue requesting full C translation : https://github.com/manuelbl/ttn-esp32/issues/38
If an app needs multiple instances of something it is much better to stay with classes, otherwise decoration with namespace is sufficient.
But wait, C doesn't even have namespaces and what a mess of identifiers this creates! No, this is bad.
I don't know if instantiating multiple entities of TTN-something over LMIC is necessary.
If this is not necessary, you can pretty much do yourself a C version easily by stripping class declarations and moving class members to .c file.
This is basic stuff, it doesn't require you to know template magic and other newer concepts.
« Last Edit: June 17, 2021, 07:46:35 am by Unixon »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #44 on: June 17, 2021, 08:13:57 am »
"const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR [...]
Of course it is; it is just a matter of link time configuration.

C nor C++ do not inherently have any notion of "const" implying anything about address space, but the base set of functions implemented for the environment can do that, using either C11 _Generic, or C++ function overloading.

[...] without breaking or bloating all of the standard C library functions, [...]
But that's exactly my point: that is an arbitrary requirement.

By incorporating avr-libc as part of Arduino, they cornered themselves.  Why rely on the standard C library function implementation, when all they do is cause you trouble?

It is true that some bloat would be inevitable.  Lets look at the memory function variants we'd need, from the users' point of view, assuming either C++ or C11 generics were used:
Code: [Select]
void *memset(void *dst_ram, int byte, size_t bytes);
void *memcpy(void *dst_ram, void *src_ram, size_t bytes);
void *memcpy(void *dst_ram, const void *src_rom, size_t bytes);
void *memmove(void *dst_ram, void *src_ram, size_t bytes);
void *memmove(void *dst_ram, const void *src_rom, size_t bytes);
void *memchr(void *dst_ram, int byte, size_t bytes);
const void *memchr(const void *dst_ram, int byte, size_t bytes);
void *memrchr(void *dst_ram, int byte, size_t bytes);
const void *memrchr(const void *dst_ram, int byte, size_t bytes);
void *memmem(void *data1_ram, size_t data1_bytes, void *data2_ram, size_t data2_bytes);
void *memmem(void *data1_ram, size_t data1_bytes, const void *data2_rom, size_t data2_bytes);
const void *memmem(const void *data1_rom, size_t data1_bytes, void *data2_ram, size_t data2_bytes);
const void *memmem(const void *data1_rom, size_t data1_bytes, const void *data2_rom, size_t data2_bytes);
Copy functions have two variants, comparison functions four.  Some functions (memset()) do not have any variants.  During linking, only the variants used are included in the final binary.  Similar list can be constructed for the string functions (str*()).

However, to avoid that Flash/ROM bloat, you now copy all string literals to RAM.  Does that sound like a good tradeoff to you?  It does not to me, especially because even avr-libc implements most of those variants with the _P or _PF suffix anyway.

In other words, the "bloat" you object to, already exists within avr-libc.

[...] not to mention [breaking] users' code.
Embedded/IoT/Arduino/etc. code does not heavily use the functions provided by avr-libc, so I am unsure of exactly how much breakage or work it would be for users to port their code to a new environment, if they agreed it was a completely new one.  Like Arduino was supposed to be, originally.

Personally, I claim that starting from scratch, and designing the functionality to give the maximum power and control to the user, would be preferable.  Yes, there would be a lot of annoyed people who hate reading documentation, and just want their C or C++ code to be copy-pastable and just work, but their code is utter shit anyway, and catering to the lowest common denominator only works if your strategy is to be cheaper than other alternatives.

As to Arduino, their build mechanism already "breaks" C/C++ expectations, so I don't really see any issue with having the base functions assume that const char * and const unsigned char * refer to immutable data in ROM/Flash (in the old PROGMEM address space), whereas char * and unsigned char * refer to mutable data in RAM.

How hard would it be to get through to users that in this Notarduino environment there are two address spaces, and that the base functions use const to indicate the code one, non-const the data one?  We could replace it with a macro, say immutable, but in the 2005-2012 timeframe it would have had to incorporate const.

Since 2012 or so, GCC has had named address space support, so instead of const, we can use __flash qualifier for pointers, indicating data in the .progmem.data section (which a linker file should map to Flash).  It is a type qualifier like const or volatile, so both C11 _Generic and C++ function overloading do differentiate signatures that only differ by __flash.  This one could be "renamed" using a preprocessor directive to whatever new keyword that does not correspond to an existing C or C++ keyword, without any problems.
« Last Edit: June 17, 2021, 08:20:54 am by Nominal Animal »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7165
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #45 on: June 17, 2021, 05:11:59 pm »
The OP didn't mention much actually. I think you read a lot from what they were asking. A bit too much.
They did.  ESP-IDF and LoRa RF are explicitly embedded/IoT stuff.  They also specifically stated "Is it something usual in MCU world?"

Feel free to disagree, but in my opinion, the context of the question is clear.  And it is not what one might think by reading only the subject title, at all.

The fact it's embedded stuff doesn't change one thing about what I said in my previous posts.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3484
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #46 on: June 18, 2021, 01:11:25 am »
Quote
Quote
"const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR [...]
Of course it is; it is just a matter of link time configuration.
Not it's not.  An AVR uses different instructions for accessing data in flash, vs accessing data in RAM.  You would have to add width to all pointers, and have all code that deals with indirect access to values (pointers, references, etc) be prepare to handle either pointers to read/write data, or to const data.   ("if (*p) == 0) {} would need to be aware, which is core compiler functionality, not just libc behavior.) That's WAY more than "a little bloat."  (it would have one of the mis-features that C programmers often ascribe to C++  - seemingly minor changes would have dramatic effects on size/performance.)

C actually added support for "named memory spaces" that could have helped solve this problem, but the C++ folks have rejected the idea.

Quote
to avoid that Flash/ROM bloat, you now copy all string literals to RAM.  Does that sound like a good tradeoff to you?
It seems to have worked pretty well up till now.  Perhaps because AVRs had so little memory that string literals were not very common, anyway.

I can vaguely imaging a "pure C++" implementation that overloaded a bunch of the normal C pointer operators that might WORK, but it sounds like it'd be a lot more distasteful than the current F() and _P() hacks...
 

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #47 on: June 18, 2021, 09:08:35 am »
"const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR [...]
Of course it is; it is just a matter of link time configuration.

The Avr8 is somehow like 8051 and exotic DSPs, where the code and data in separate belong to separate spaces and you need an "instruction bridge" to load something from the code space and use it as a constant, otherwise the code space would only be accessible for fetching op-code.

I had the same problem with a couple of weird Japanese graphing calculators; surprisingly the official C compiler was not Gcc but rather a proprietary  tool capable of automatically figuring out when to use the "bridge instructions[/ i]".

So whenever you write "const" in front of a variable declaration, the machine level correctly understands that you want to have a constant within the flash and instantiates a bridge instruction" to manage it.

Sweet, but it's now how Gcc-Avr8 does its job  :-//
« Last Edit: June 18, 2021, 10:02:06 am by DiTBho »
 

Online langwadt

  • Super Contributor
  • ***
  • Posts: 2591
  • Country: dk
Re: Crazyness: C and C++ mixing
« Reply #48 on: June 18, 2021, 09:18:46 am »
"const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR [...]
Of course it is; it is just a matter of link time configuration.

The Avr8 is somehow like 8051 and exotic DSPs, where the code and data in separate belong to separate spaces and you need an "instruction bridge" to load something from the code space and use it as a constant, otherwise the code space would only be accessible for fetching op-code.


https://en.wikipedia.org/wiki/Harvard_architecture
https://en.wikipedia.org/wiki/Von_Neumann_architecture
 
The following users thanked this post: DiTBho

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #49 on: June 18, 2021, 09:51:25 am »
Quote
Quote
"const" does not and can not be sufficient to put immutable strings in Flash on (traditional) AVR [...]
Of course it is; it is just a matter of link time configuration.
Not it's not.  An AVR uses different instructions for accessing data in flash, vs accessing data in RAM.  You would have to add width to all pointers, [...]
I am fully aware!  But you completely misunderstand.

We can use const in the base function signatures to indicate which address space the pointer parameter is in.

The pointer itself does not carry the information; the function signature does, and the functions are overloaded to handle this transparently without the user having to use different name for the function depending on the pointer type as one has to with avr-libc (_P(), _PF() suffixes).

Remember, avr-libc already has all of these variants, so "code bloat" is not a valid argument.

Here is an example implementation of strcmp() for avr-g++:
Code: [Select]
#include <avr/pgmspace.h>
#define  FLASH  const
#define  RAM

__attribute__((noinline, weak))
int lib_strcmp_ff(FLASH char *s1, FLASH char *s2)
{
    while (1) {
        const unsigned char  c1 = pgm_read_byte(s1++);
        const unsigned char  c2 = pgm_read_byte(s2++);
        if (c1 != c2)
            return (int)c1 - (int)c2;
        if (!c1)
            return 0;
    }
}

__attribute__((noinline, weak))
int lib_strcmp_fr(FLASH char *s1, RAM char *s2)
{
    while (1) {
        const unsigned char  c1 = pgm_read_byte(s1++);
        const unsigned char  c2 = *(unsigned char *)(s2++);
        if (c1 != c2)
            return (int)c1 - (int)c2;
        if (!c1)
            return 0;
    }
}

__attribute__((noinline, weak))
int lib_strcmp_rr(RAM char *s1, RAM char *s2)
{
    while (1) {
        const unsigned char  c1 = *(unsigned char *)(s1++);
        const unsigned char  c2 = *(unsigned char *)(s2++);
        if (c1 != c2)
            return (int)c1 - (int)c2;
        if (!c1)
            return 0;
    }
}

static inline int  lib_strcmp(FLASH char *s1, FLASH char *s2) { return  lib_strcmp_ff(s1, s2); }
static inline int  lib_strcmp(FLASH char *s1, RAM   char *s2) { return  lib_strcmp_fr(s1, s2); }
static inline int  lib_strcmp(RAM   char *s1, FLASH char *s2) { return -lib_strcmp_fr(s2, s1); }
static inline int  lib_strcmp(RAM   char *s1, RAM   char *s2) { return  lib_strcmp_rr(s1, s2); }

Here is a trivial example of how the above can be used, using current avr-libc notation for Flash storage:
Code: [Select]
const char  m1[] PROGMEM = "First string, in Flash";
const char  m2[] PROGMEM = "Second string, also in Flash";
char        m3[] = "Third string, in RAM";
char        m4[] = "Fourth string, also in RAM";

unsigned char  test1(void)
{
    return ((lib_strcmp(m1, m2) < 0) ?  1 : 0)
         | ((lib_strcmp(m1, m3) < 0) ?  2 : 0)
         | ((lib_strcmp(m1, m4) < 0) ?  4 : 0)
         | ((lib_strcmp(m2, m3) < 0) ?  8 : 0)
         | ((lib_strcmp(m2, m4) < 0) ? 16 : 0)
         | ((lib_strcmp(m3, m4) < 0) ? 32 : 0);
}

unsigned char  test2(const char *fs1, const char *fs2, char *rs1, char *rs2)
{
    return ((lib_strcmp(fs1, fs2) < 0) ?  1 : 0)
         | ((lib_strcmp(fs1, rs1) < 0) ?  2 : 0)
         | ((lib_strcmp(fs1, rs2) < 0) ?  4 : 0)
         | ((lib_strcmp(fs2, rs1) < 0) ?  8 : 0)
         | ((lib_strcmp(fs2, rs2) < 0) ? 16 : 0)
         | ((lib_strcmp(rs1, rs1) < 0) ? 32 : 0);
}
Both test1() and test2() will call the correct variants of the lib_strcmp() function.
test2() assumes fs1 and fs2 to point to Flash/ROM, and rs1 and rs2 to RAM.

See? We can exploit the type of the parameter to determine which address space it points to.  We do NOT need to record that information in the pointer itself.

Furthermore, GCC uses a separate section for const and non-const file scope variables and objects, so it is a trivial matter of mapping file-scope const variables and objects to ROM/Flash.  Dropping the standard C library implementation from avr-libc, and implementing optimized versions of above (remember, I already pointed out GCC can provide many of these functions as built-in optimized versions), would make for a better IoT programming environment.
« Last Edit: June 18, 2021, 09:56:22 am by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #50 on: June 18, 2021, 10:07:30 am »
https://en.wikipedia.org/wiki/Harvard_architecture
https://en.wikipedia.org/wiki/Von_Neumann_architecture

Thanks for the links, but my * confused face * at the end of my previous post is only related to the last sentence I wrote: I mean, the part where a custom C compiler automatically understands when to use "bridge instructions" whereas gcc-Avr8 prompts the user to specifically request.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #51 on: June 18, 2021, 01:01:13 pm »
In essence, what I explained above, is that in this "freestanding C plus subset of C++" environment, we can define the programming language pretty much as we want.
If we do not, and rely on existing standard libraries or subsets of them, we will have to make the kinds of compromises Arduino environment suffers from.

Therefore, I once again claim that it is better to start from minimal set of assumptions and expectations, or you'll bind yourself to suboptimal solutions.

I have shown above that const pointer parameters can be redefined to mean "the target is in ROM/Flash".
With C or C++ specific features and the linker, we can redefine const to mean "this object is in ROM/Flash", and have the compiler generate correct address space access instructions.

For code compiled using gcc, we can use the __flash attribute, and the compiler will handle everything for us.  For example, on AVRs, if you declare const __flash struct mystruct  foo = { ... };, then foo will be placed in the .progmem.data section, and all accesses to members of foo will use the LPM instruction (or equivalent).  The compiler knows it is in a different address space, and will honor that.  _Generic does include __flash in type specifications, so we can create generic functions (like strcmp() etc.) with variants for the different address space parameters.  Again, avr-libc already has these _P() and _PF() variants, so "that will bloat code" is not really a valid argument; all this does is let the programmer choose how they balance RAM and Flash/ROM use, instead of forcing a specific choice on them.

For code compiled using g++, we cannot use __flash even inside extern "C" { ... } as g++ is a C++ compiler and not a C one, and does not have named address space support.  This means that while we can trivially have const objects and variables placed in ROM/Flash, it is much harder to get g++ to generate the correct accessors.  One solution is to implement dedicated types/classes for objects and variables stored in ROM/Flash.  An even better solution is to use Clang for AVR, since it supports named address spaces in C++ also.

Obviously, it is a completely different question whether one should do that to const or not.  (The way I'd prefer is a bit more complicated.)
My point is that it is possible, if you just release your fixation on the standard hosted environments, and accept starting fresh.
« Last Edit: June 18, 2021, 01:38:34 pm by Nominal Animal »
 
The following users thanked this post: netmonk, DiTBho

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #52 on: June 18, 2021, 09:46:07 pm »
From https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html

Quote
As an extension, GNU C supports named address spaces as defined in the N1275 draft of ISO/IEC DTR 18037. Support for named address spaces in GCC will evolve as the draft technical report changes. Calling conventions for any target might also change. At present, only the AVR, M32C, RL78, and x86 targets support address spaces other than the generic address space.

So i dont think such thing is available for esp32 from what i read. Or may be i misunderstood something.
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3484
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #53 on: June 19, 2021, 07:21:01 am »
Quote
use Clang for AVR, since it supports named address spaces in C++ also.
Really?  I thought the C++ folks had explicitly rejected the idea.  Was that just the Gnu GCC C++ project?

Quote
i dont think such thing [as named address spaces] is available for esp32 from what i read.
I didn't think ESP32 needed anything?  at least, the the Arduino "AVR Compatibility" features have null macros for things like PSTR and PROGMEM.
ESP8266, OTOH, is worse than AVRs (IIRC, flash can only be accessed 32bits at a time, or something like that.)  It's somewhat to Arduino's credit that their F() and related "hacks" can be made to work on ESP8266.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #54 on: June 19, 2021, 09:10:55 am »
So i dont think such thing is available for esp32 from what i read. Or may be i misunderstood something.
Only Harvard architectures, with separate address spaces for RAM and ROM/Flash, need the support.  Architectures where there is a single address space, like ARMs (and RISC-V's, I believe, like ESP32-S2), you do not need it at all.

On Harvard architectures, the same address can refer to different things, depending on which instruction is used to access it.  On AVR, normal machine instructions access RAM, and the LPM/eLPM instruction accesses ROM/Flash.

Quote
use Clang for AVR, since it supports named address spaces in C++ also.
Really?  I thought the C++ folks had explicitly rejected the idea.  Was that just the Gnu GCC C++ project?
Yes, Clang does support named address spaces, even in templates.  See the rationale paper (PDF).

You only need to add __attribute__((address_space (1))) to the variable or the type, and Clang/LLVM will generate the correct access instructions.  A minimal example, example.cpp:
Code: [Select]
const volatile __attribute__((address_space (1))) int  foo = 5;
int get_foo(void) { return foo; }
Compile using clang++-10 -ffreestanding -O2 -Wall -target avr -mmcu=atmega32u4 -c example.cpp, and avr-objdump -D example.o will tell you
Code: [Select]
Disassembly of section .progmem.data:

00000000 <_Z7get_foov>:
   0: e0 e0        ldi r30, 0x00 ; 0
   2: f0 e0        ldi r31, 0x00 ; 0
   4: 85 91        lpm r24, Z+
   6: 94 91        lpm r25, Z
   8: 08 95        ret

0000000a <foo>:
   a: 05 00        .word 0x0005 ; ????
as expected.  (The address of <foo> shows as zero, because avr-objdump -D/-d does not apply relocations; this too is as expected, and will be fixed when linked to the final binary.)

Similarly, defining e.g. typedef  int  rom_int __attribute__((address_space (1))); will put rom_int x; in .progmem.data section, and generate LPM instructions for accessing it.

I am not sure which version of clang (on Ubuntu, which this particular machine I'm using runs on) added AVR support; clang++-6.0 does not have AVR support, but clang++-10 does, and both versions are standard (Universe) Ubuntu packages.  So somewhere in between.  G++ simply ignores the attribute (with a warning if -Wattributes is enabled), and obviously generates wrong code.

As I understand it, the C++ standards committee and GCC C++ frontend developers have rejected named address spaces as "too hard to implement correctly for templates et cetera", but the clang/llvm folks just went and implemented it nevertheless.

It is quite likely that right now, clang is the better C++ compiler for AVR, but I haven't done enough testing (or even tried to find related LLVM/clang/GCC bug reports) to be sure; but I definitely would not use GCC 9 or later on AVR because of the known-but-not-yet-fixed bugs.

My own (limited!) testing on Pro Micro clones (AVR, ATmega32u4, with native USB) does indicate that for embedded code doing command/response and data transfer work on behalf of the host computer, the C++ overloaded code with literal strings in Flash (and essentially a PROGMEM/ROM/FLASH attribute that differentiates types at compile time as being located in ROM/Flash instead of RAM), yields simpler and more intuitive code, and uses less resources.  The fact that some base functions (especially print family, for formatting string data to a serial connection, be it USB or UART or SPI or whatever buffer) do end up compiling to more than one variant, does not seem to cause the bloat you feared would occur.

Then again, that –– sensor interfaces, small external displays for embedded devices running Linux on an SBC, and so on –– is the sort of thing I'm interested in, so I do not claim it is a surefire fix for all the faults in Arduino!  I only claim it leads to more intuitive code, and gives the programmer full control over where the data is placed.

Hopefully, others can see how powerful this is, if one is not tied to existing code, especially existing standard library implementations.  Me, I'm quite happy to implement my own low-level routines using extended inline assembly, so I'm happy as a clam here.
« Last Edit: June 19, 2021, 09:16:21 am by Nominal Animal »
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 3565
  • Country: pl
Re: Crazyness: C and C++ mixing
« Reply #55 on: June 19, 2021, 09:14:29 am »
FYI, modern AVRs have memory mapping of flash, accessible with LD/ST.

And I'm not sure if I understood the previous discussion correctly, but I'm pretty sure you cannot substitute 'const' for address space identifier in absence of proper address spaces support in the compiler. Too easy to screw things up with casting. Nevertheless, I think some ancient GCC actually worked like that anyway, before they added 'progmem'.
« Last Edit: June 19, 2021, 09:20:02 am by magic »
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #56 on: June 19, 2021, 09:18:46 am »
FYI, modern AVRs have memory mapping of flash, accessible with LD/ST.
Perhaps you should clarify what you mean by "modern", considering ATmega32u4 is still in production and heavily used, and sadly affected by Chipageddon too.  (I know which ones you mean – not the 8-bit ones –, but others reading this thread may not.)
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 3565
  • Country: pl
Re: Crazyness: C and C++ mixing
« Reply #57 on: June 19, 2021, 09:22:50 am »
So-called 1-series and 0-series. Perhaps XMEGA too. They aren't fully compatible with traditional AVR, hence somewhat obscure. They are 8 bit, but peripherals have changed.
 
The following users thanked this post: Nominal Animal

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #58 on: June 19, 2021, 10:10:06 am »
They aren't fully compatible with traditional AVR, hence somewhat obscure. They are 8 bit, but peripherals have changed.

Yup, two years ago, I abandoned them for exactly this reason.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #59 on: June 19, 2021, 10:28:48 am »
So-called 1-series and 0-series. Perhaps XMEGA too. They aren't fully compatible with traditional AVR, hence somewhat obscure. They are 8 bit, but peripherals have changed.
Thanks!  I wasn't aware that e.g. TinyAVR 0, 1, and 2 all have an unified address space!  I thought it was only with the very new special-purpose AVRs.

The chip naming is odd.  I guess for ATtinyNFV and ATtinyNNFV, N or NN is Flash size, F is the family (0, 1, 2), and V is variant indicating number of pins and possibly SRAM size.

At the hardware level, they still have separate SRAM and Flash address buses; the TinyAVR core just maps them to a single unified 16-bit address space accessible with the standard LD/ST machine instructions.  In the unified address space, I/O registers, EEPROM, and SRAM are in the lower half of the address space (0x0000 to 0x7FFF), and Flash is in the upper half of the address space (starting at address 0x8000).

The Flash address space can still be accessed using LPM, but then the addresses start at 0x0000.  Essentially, the data address space is shrunk to 15 bit addresses, and the most significant bit enables Flash address space translation, so that Flash addresses 0x0000-0x7FFF are accessible in the data address space at 0x8000-0xFFFF.
(According to the TinyAVR 2 datasheet, indeed, loading a byte from address 0x8000 using LD is the same as loading a byte from address 0x0000 using LPM.  Funky!)

So, the complex answer is that TinyAVR 0, 1, and 2 families do have Harvard architecture and do support LPM for Flash access, but they also have address magic in the hardware so that Flash is also available in the normal data address space.
 

Offline magic

  • Super Contributor
  • ***
  • Posts: 3565
  • Country: pl
Re: Crazyness: C and C++ mixing
« Reply #60 on: June 19, 2021, 10:52:34 am »
It's FFPI: flash, peripherals, I/Os.
RAM is proportional flash.

Yes, it's more or less a standard AVR core but something traps loads above 0x8000 and redirects them to flash.

I wasn't aware of 2-series, it looks like they lose some peripherals for a better ADC.
 

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #61 on: June 19, 2021, 11:41:38 am »
address magic in the hardware

if you implement a RISC softcore in FPGA you basically have the same problems and you will probably end up with the same solutions:
  • "software bridge" dedicated instructions
  • "hardware bridge" dedicated circuits, usually achieved with a dual port mapping

I call them "bridge" because they somehow connect two different spaces: code-space <==> data-space

Personally I prefer the first approach for safety reasons. In my case, all the "software bridge" instructions that need to write something are privileged, hence only the kernel can use them to reprogram (e.g. the bootloader), while reading a constant is always allowed.

I could achieve the same goal with the other approach, but it would require extra hardware like a circuit that checks if an address belongs to the allowed range for a given operating mode.

E.g. don't allow a task in user-space to write things in the range 0xE000.0000..0xf000.000, because there it's mapped the code-space, and only the kernel can write there.

It's doable, just it costs more hardware and effort.
 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #62 on: June 19, 2021, 12:12:56 pm »
Yep.  A simplified summary about address spaces is that in C, both gcc and clang handle them just fine; and in C++, clang++ handles them just fine, but g++ does not.

And by "handle correctly", I mean the compiler will generate correct access instructions and place the variables and objects in the correct sections without having to use helper functions or macros for the accesses, as long as either the variable or the type includes the address space attribute.

It's not just older AVRs that have multiple address spaces, like DiTBho mentioned (about same in FPGA implementations); OpenCL and even x86-64 have them, too.
On x86-64, named address spaces are used to indicate specific segment register must be used; clang uses address_space(256) for GS, address_space(257) for FS, and address_space(258) for SS, and has clear rules on how these work through casts etc.  (gcc end uses __seg_fs and __seg_gs; g++ does not support them.)
The special segments are used for example for thread-specific data, as well as some very nifty kernel-userspace transparent data stuff.

Version 9 clang/LLVM language extensions lists these.  It looks like OpenCL drove the need for named address space support in clang; hopefully, GNU folks will change their mind and come along.  This is definitely useful.
« Last Edit: June 19, 2021, 12:15:26 pm by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline netmonk

  • Contributor
  • Posts: 24
  • Country: fr
Re: Crazyness: C and C++ mixing
« Reply #63 on: June 19, 2021, 01:08:58 pm »
So yesterday i was reading gcc documentation after digging for named address space. And i was trying to focus on how to build a .bin.
Well i guess gnu doc repository is not the place for such information.

For exemple, when i was playing with my riscV gd32 i found this blog post https://vivonomicon.com/2020/02/11/bare-metal-risc-v-development-with-the-gd32vf103cb/ very interesting but sadly im not familiar with 99% of the content:
- linker script
- vector table
- boot

My thinking is "ok let's learn C++ freestanding" but it's like when i read the K&R long time ago, beside playing with language concept, it was difficult to apply to real programming (back in the time i was trying to code a demo on msdos) K&R is unable to teach you how to play with cpu/vga card/sound blaster card/ and so on.

I feel pretty the same now if i want to go the hard way. I can setup an esp-idf framework ready to go, but lots of work and pitfall are hidden. It's like with Arduino, it's very fast to code a temp sensor pushing data over wifi in few line of code. But when you want to play with a peripherial at full extent, you feel rapidly limited with such env.

So where is it possible to find the fundamentals of MCU development from how to start with an empty host os (which tool to install) an empty directory (which file are mandatory and for what) to produce a working firmware ?


 

Offline Nominal Animal

  • Super Contributor
  • ***
  • Posts: 2822
  • Country: fi
    • My home page and email address
Re: Crazyness: C and C++ mixing
« Reply #64 on: June 19, 2021, 03:19:52 pm »
I can setup an esp-idf framework ready to go, but lots of work and pitfall are hidden. It's like with Arduino, it's very fast to code a temp sensor pushing data over wifi in few line of code. But when you want to play with a peripherial at full extent, you feel rapidly limited with such env.
Very true.

That's why I've been mentioning the ATmega32u4 so often.  On one hand, it is simple and one can basically grasp all of it.  On the other hand, because it does have native USB support (12 Mbit/s), it can appear as whatever USB device you want, including USB HID devices like keyboards and joysticks and gamepads, as an USB Serial device, or even some native USB device (like slower USB audio devices – note the 12 Mbit/s limitation).

Starting from those (well, actually AT90USB1280 aka Teensy 2.0++, then 2.0, then to Teensy 3.0, a diversion back to ATmega32u4 via cheap Pro Micro clones using Arduino Leonardo bootloader, and so on), I could grow my understanding and capabilities step by step, without encountering a sheer vertical wall at any point.

You can even look at the intermediate files the Arduino environment generates, and explore the C runtime (crtmcu.o object files linked with Arduino code for each MCU), to see exactly how stuff gets implemented.

Most of the technical details I've mentioned in this thread are advanced stuff, in the hopes that you and others reading this thread will someday reach the point where they can make informed choices, and remember this thread and my points; and make stuff that Is Just Better than what we have now.
It is perfectly acceptable – and personally recommended by me – to start with the "easy" environments like Arduino, as getting stuff working gives that delicious boost of success and keeps ones motivation up; and while the environments are less than perfect, they are still quite usable, and very good for learning (as one quickly gets visible feedback, instead of just a laconic "Ok, that worked", and can play with stuff).  Just keep in mind that it is just a step along the ladder – optional, though; not everybody needs to create stuff for others, it is perfectly okay to just play with stuff for fun – towards better control and mastery of the subject domain.

So where is it possible to find the fundamentals of MCU development from how to start with an empty host os (which tool to install) an empty directory (which file are mandatory and for what) to produce a working firmware ?
I wish I had a real answer to that!

I think –– but I seriously hope others will pipe in, because I'm not at all sure about this –– that the best approach is to start in the Arduino or PlatformIO environment with preferably a simple microcontroller, and learn to write code in this funky environment (while being aware of what are quirks of a particular environment, what is hardware-specific, and what is C or C++), and when you have something working (even a blinky one), in parallel start to examine the build process and intermediate files part by part.  If the microcontroller is suitable for bare metal development, then the "advanced" step would be to recreate something you did in the Arduino/PlatformIO environment in "bare metal".

I do definitely recommend getting development experience in Arduino/PlatformIO first with different microcontrollers and hardware types.  Not only is it different, but you'll find you yourself will shift paradigms based on the task at hand.  Something like an USB arcade controller with joystick and buttons is nice to do in freestanding C, but a complex configurable robot firmware with different compile-time selectable modules can be much easier to write using the C++ subset.  It is not just the language, but also how you go about looking for a way to implement something.

The best example I can think of is how the different display module libraries work.  Some of them have a full frame buffer, others only have a partial buffer, and you need to repeat the drawing commands (they only capture/rasterize to the current buffer slice) until the entire buffer has been generated and sent to the device.  So, for small memory devices you prefer to deal with the drawing primitives, and for larger memory devices you can operate on the entire frame buffer and can track changes (so that unnecessary parts are not updated, and the overall updates are faster – especially useful with eInk/e-paper displays).  It does not matter what language you use, but for efficient implementation, your approach will differ based on whether you expect to have SRAM for a full framebuffer or only a part of one.

Even though I think Arduino could be better, I still think it is a very good environment to learn, and to do interesting stuff and gadgets in.
For example, I designed a Pro Micro Gamepad so that you could solder a Pro Micro clone on top of the (bottom board) upside-down, add an 128×32 (0.91") I2C OLED display, and program the entire thing in the Arduino environment (treating it as an Arduino Leonardo there, but using the Pro Micro pinout to see how the hardware pins are actually connected to the gamepads).
There is absolutely no need to do this in bare metal; the Arduino version will work just fine.
(The idea is that the gamepad is actually a gamepad + keyboard combination, and generates keypresses when the buttons are pressed, so that one can play Online games with it using standard keyboard controls.  The two smaller tactile buttons are intended to switch between mappings, with the OLED display showing the currently selected mapping for a few seconds after the most recent small tactile button press.  It's a laughably simple design, so I designed another based on CH551g, but haven't built that yet either.)

linker script
Linker script is a linker configuration file.  You can find these in various projects in a ldscripts directory.  The suffix determines the set of linker parameters used;

For both gcc and clang, you can specify a custom linker script using -Wl,-T -Wl,filename .

The default ones for avr-binutils are created by a shell script named avr.sc in the binutils sources.

You can find a quick synopsis at OSDev Wiki, and full details at the binutils ld scripts documentation.

This is what determines where code starts, where the fuse bits are, where data will be put, and so on.

vector table
Vector table is either an array of code addresses, or an array of jump instructions to code addresses.  When the microcontroller starts up, or a hardware interrupt occurs, it will use a specific entry in the vector table.

For ATmega32u4 (avr5), the vector table is in Flash, four bytes per entry (0x0c 0x94 0xLL 0xHH for a jump to address 0xHHLL), with the first (zeroth) one being the init/reset vector, that the MCU starts with when it powers on or resets.

boot
When a microcontroller starts up or resets, it is in a very hardware specific state.  To make firmware upgrading easy, the very first code it executes is a bootloader.  Essentially, its purpose is to decide whether the microcontroller should expect a new firmware to be uploaded, or just jump to currently existing firmware.

Arduino uses its own bootloaders, so do Teensies.  There is an USB standard, Device Firmware Upgrade or DFU, that microcontrollers with native USB interfaces can support for updating the device firmware via USB.  In many cases, the bootloader will not replace itself, only the rest of the Flash contents, and to replace the bootloader, one needs to use the in-system programming interface (JTAG, for example) appropriate for the hardware.  While the bootloader is running, it can use the entire SRAM as it wishes, since once it hands control over to the existing firmware, the firmware will be in full control.  So, the only limited resource bootloaders consume, really is just the Flash they take up.

Therefore, "booting" in this context means the phase in microcontroller startup and reset sequences, where the "bootloader" code checks whether a new firmware should be uploaded to that microcontroller, or whether it should just hand over the reigns to the currently installed firmware.

Interestingly, the proprietary bootloader for Teensies, Halfkay, uses the HID protocol (and therefore needs no special OS drivers!), and the 8-bit AVR implementation (for Teensy 2.0 and 2.0++) takes less than half a kilobyte of Flash.  This made early Teensies so much nicer to develop on than e.g. Arduinos.
« Last Edit: June 19, 2021, 03:26:12 pm by Nominal Animal »
 
The following users thanked this post: DiTBho

Offline DiTBho

  • Frequent Contributor
  • **
  • Posts: 878
  • Country: gb
Re: Crazyness: C and C++ mixing
« Reply #65 on: June 19, 2021, 03:47:19 pm »
I wish I had a real answer to that!

This also depends on the tool-chain. For example, a couple of commercial tool-chains I need to use are for Windows and they just need to click "setup.exe" and follow the video instructions.

examples:
Analog Devices DSP Visual Studio ++
CodeWarrior Studio

(I don't like Windows, but .... I have to say, the instructions you have to follow on Windows XP are the same you have to follow on Windows 10, and the development tool is exactly the same, that's a good point!)

In these examples, it's all included, and it also includes the support for the debugger, as well as templates and "get started" examples for the bootstrap code.

Linux and FreeBSD have a different approach with G-CC or Clang, and it's a rather "rolling" approach, if you read an "how to" article today, the given instructions may become out of date the next day due to the volatile nature of the tools involved and you need more specific knowledge and skills to manage them as well as the things required by "ecosystem".

So I think there is no simple answer, and only practical experience can tell and teach  :-//
« Last Edit: June 19, 2021, 03:49:31 pm by DiTBho »
 

Offline westfw

  • Super Contributor
  • ***
  • Posts: 3484
  • Country: us
Re: Crazyness: C and C++ mixing
« Reply #66 on: June 19, 2021, 10:20:41 pm »
Quote
I wasn't aware that e.g. TinyAVR 0, 1, and 2 all have an unified address space!
Also the "mega0" line (ATmega4809) manages to map a full 48k of Flash space into the 64k unified address space, and the newer AVRmmDxpp can map 32k at a time (of their up-to-128k) into unified addresses via yet another bank switching scheme.  (I'm not sure that's good for C in general, beyond pretending that you have an aways-there 32k, but...)
Quote
Quote
["xtiny"] aren't fully compatible with traditional AVR, hence somewhat obscure. They are 8 bit, but peripherals have changed.
Yup, two years ago, I abandoned them for exactly this reason.
You should probably reconsider, IMO.  The changes in peripherals are "compatible with modern practices", the new features are useful, Microchip has dontinued the line enough that I'm pretty confident it's their vision of the future, and they have significantly more RAM/flash/perhipherals than the previous generations at a lower cost.  (Still no USB or 8pin/8kB chips, though.  The 32u4 and tiny85 must be real cash cows.)

Quote
im not familiar with 99% of the content:
- linker script
- vector table
- boot
Yes, there's quite a bit of effort that goes on in the process of putting "generic" output from a compiler into an address space (or two) that isn't flat, contiguous, isn't read/write everywhere, and has "special addresses" containing "magic stuff."  Fortunately, compiler writers have kept up pretty well (I can remember when "produces ROMable code" was a selling point for 8088 compilers), and the "usual cases" don't require manual intervention.  It's worth learning about, at least "some."   (it's vaguely similar to the shock that new assembly language programmers go through when they are faced with not having initialized RAM.)
 
The following users thanked this post: Nominal Animal, netmonk, DiTBho


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf