C++17’s Useful Features For Embedded Systems

Although the world of embedded software development languages seem to span somewhere between ASM and C89 all the way to MicroPython, there is a lot to be said for a happy medium between ease of development and features that makes the software more robust without adding overhead or bloat to the final firmware image.

This is where C++ has objectively many advantages over even C99, and as [Çağlayan Dökme] argues in a recent blog post C++17 adds many developer critter comforts to C++98 and the more recent C++11 C++14 standards.

First stepping back a generation (technically two, with C++20 also being a thing already), the addition of binary literals (e.g. 0b1010'1100) in C++14 and the expanded use of constexpr is addressed, with the latter foreshadowing C++17’s increased focus on compile time optimizations. A new attribute in C++17 that is part of this is [[nodiscard]], which when added before to the return type of a function or method requires the return value to be used in some manner, much like with functions in Ada (contrasted with procedures).

As [Çağlayan] notes, the biggest strength of compile-time checks is that it can save a lot of deploy-test-fix round-trips, with the total number of issues caught after deployment that could have been caught during compilation ideally being zero. Here C++17 streamlines the static_assert() mechanism and simplifies using if constexpr to instantiate code depending on compile-time conditions. Beyond compile-time optimizations there are a few other niceties, such as C++17 guaranteeing copy elision (return value optimization) when an object is returned directly, which is a welcome feature in hard real-time environments.

With today even MCUs having enough grunt to run multi-threaded applications and potentially firmware compiled from a many-thousand LoC codebase, picking a programming language that assists the developer with such an arduous task is very important, with Ada being the primary choice for high-reliability embedded platforms, but C++ along with C enjoying the most widespread (free) compiler support. Even if C++ isn’t supported on every single MCU out there (8051-based and most PIC MCUs mostly), whenever it is an option, it’s a pretty solid choice, especially with knowledge of these new language features.

42 thoughts on “C++17’s Useful Features For Embedded Systems

  1. Good tips. C++ is great for embedded code.

    And now, let the C++ and C hate begin. If you don’t know where to start, you can start telling how horrible pointers are for example. Then you can continue to topic where you claim how object oriented code is unefficient for embedded use. After that you can throw in Rust card and convince how it solves all troubles and doesn’t bring any new problems.

    1. I hold a degtee in computer science and I have written code in about 15 “grown-up” languages and 20 or so more special-use languages … but never a line of proper C or C++, so I’m in no position to have an opinion about it!

      Althoug I write arduino code in Processing which apparently is C++, but have not yet had the need to find out what a pointer is.

      1. For anyone that doesn’t know what a pointer is, all it is is a variable that stores the memory address of the value instead of the value itself. It is useful for a variety of things like arrays where you know the size of each element in the array and can figure out the address for each element in the array.

        1. One usually doesn’t need pointers for arrays.
          In the C language though one can use pointers and dynamic allocation for implementing variable size arrays. Note that for local variable size arrays there is a built in support in the C99 standard.

          Where pointers are much more useful is in data structures like linked lists, trees and graphs. In these structures in each node there might be one or more pointer members, which literally point to other members in the data structure.

          And while the pointer type in C actually stores an address in memory, a pointer can also be just an index in an array.
          (Of course memory itself can be viewed as an array, so you see that it is the same concept). This can be useful if one wants to reduce memory usage. The size of the memory address is usually too big.

    2. Long ago, I learned some Ada and thought that it was the language that tried to do everything.
      Then C++ came along and said “Hold my beer”.

      C is a compact language that you can learn and keep all the bits in your head.

      C++ has so many bits, with more being added all the time, that it’s impossible (for most normal humans) to keep all the bits in your head. Moreover, it allows people to write code that is totally inscrutable. And I’m not even talking about people intentionally trying to write inscrutable code. It used to be possible to look at code and understand how to find all the definitions and figure out the control flow. These days, a sophisticated IDE is required to figure those things out.

      If you don’t know what I’m talking about, you’ve not tried to read enough of other people’s code.

      I’m not saying C++ is terrible. It’s a power tool that allows implementation details to be hidden away, which is great except for when things don’t work right because you can’t figure out those details (because documentation about them almost never exists, and now the code is inscrutable).

  2. Constexpr’s are nice, but they have some gotchas that compiler’s don’t necessarily warn about. For example, using unions for type punning is not supported at compile-time, but I’ve had GCC accept the code and just give garbage output.

    Also for anyone who doesn’t yet know about it and uses C++ for embedded projects, https://www.etlcpp.com/ is worth checking out.

  3. c++ for embedded devices was a great step forward, as long as you don’t go crazy with it (though that is true with c++ in general). The 4 main principles of OO ie Abstraction/Encapsulation/Inheritance/Polymorphism are all great to use when talking to bits of hardware in the chip, and the outside world.

    I swapped from C/Assembler to C++/Assembler on embedded hardware about 25 years ago, and it made my code more robust, easier to debug, and easier to change. Though that might be because I swapped to c++ on the PC (etc) about 40 years ago, so already knew it..

  4. I started using C++ on uC’s some 20 years ago, and it’s a quite amazing language, but I have not written much code in the last 10+ years, and many details have become rusty.

    My best guess as of why people don’t like it is because of it’s complexity, and of it’s backward compatibility with the C and C++ languages of 40+ years ago. These add a lot of things that remain in the language for compatibility, but should not be used in modern application of the language.

    I’m also, wondering what you guys (and gals) think about templates. Templates can easily result in horrible code bloat when used inappropriately (C++ assumes the user knows what he’s doing), but they can also result in beautiful and compact code that is easy to understand. One of the trouble with templates is that the templates themselves are complex to write and difficult to debug, and that is another reason I’ve never put much energy into working with templates myself. But I’ve seen some truly amazing examples of the uses of templates in embedded C++.

    For example, everyone knows how horrible the handling of generic I/O pins is in “arduino”. With templates you can simple create a derived Led object, and then turn the led on or off. And the compiler can optimize a function for toggling the Led into a single asm instruction. It’s all handled during compile time with no runtime penalties. Such a use of templates makes I/O also extremely portable between completely different uC families. The code stays the same, you just link in different templates.

    A few examples of generic uC libraries based on templates:
    XPCC
    Kvasir
    BMTK
    Jeelabs / Jeeh
    And a youtube video from the old (young?) David from EEVblog:
    https://www.youtube.com/watch?v=A_saS93Clgk

    1. Like all powerful tools, C++ gives you lots of ways to damage yourself. The key to get started with C++ and with each of its features is to start small and simple. Start with just the basics and ignore all of the advanced features like templates and lambda functions. Get comfortable with pointers first and ignore references until you understand pointers. As you learn more and start tacking more complex problems, you’ll run into things you wished could be done more easily or efficiently. That’s when start looking at the more advanced features and learning how they make your problem easier. In my opinion, getting started with C++ can almost be as simple as getting started with BASIC.

      Templates, as you mentioned, can be used in a lots and lots of extremely complex (and amusing) ways and can be very intimidating. How to learn about it? Use it first as just as a way to code an algorithm once and use that code with any appropriate class without requiring a base class and without resorting to preprocessor macros. Use restraint–don’t go wild and start do crazy things with templates until you understand when and why to use them. For me, I don’t use templates most of the time but for the cases where I do, I’m glad templates are in the toolbox.

    2. I’m a big fan of templates, coming from a C# and Rust background and now finding myself in C++ (yeah, I know, I’m doing it backwards). Used well, I find it helps me express the meaning of my code — I like to lean heavily on the type system as a way of expressing meaning, especially the idea of “making impossible states impossible” (MISI). The heavy power tools for implementing MISI in C++17 are the discriminated union types `std::variant` and `std::optional`, because you can couple an option to a bunch of extra data — e.g., fields X and Y only make sense if field Z is option A, and you should have to give null/default values for X and Y if field Z is options B or C.

      That said, I really wish you could add constraints to a template parameter. It bothers me that I don’t get code intel on generic params; I just kinda have to wait until compile time to discover all the invalid code I’ve written.

          1. You’re welcome I think you’ll like it! They are a bit odd in their syntax at first. If it gets tricky, I suggest starting with the “requires” keyword. In my limited use of it I (usually) am able to #if __cpp_concepts things out for pre c++20 compat as well~~

  5. i like C because the cost of getting up to speed was low, and i paid it decades ago.

    a lot of C++ advocacy focuses around the advantages of not needing to know the inside of every object, but on embedded that’s much less of an advantage. and generally, at its best, C++ replaces the burden of understanding each object with the burden of understanding the language…and that can be a very firm requirement on embedded, where (for example) implicit copying can be too expensive.

    otoh, a lot of “embedded” these days is basically a $2 supercomputer and you can afford all sorts of wasteful overheads!

    if you know what you’re doing, the new C++ features can be useful — constexpr, in particular, has grown to (sometimes!) fulfill the false promises we all heard about templates back in the 90s.

    but the thing about modern C++ is that they keep adding to the language, but not taking away from it. it’s become a huge surface you ought to know, and the requirement to limit yourself to some subset of the language so that you can make useful assumptions is even harder than it was back when people were handicapping their C++ so that it could build on old compilers that only implemented the “annotated reference manual” features.

    but in embedded you wind up limited to subsets of languages and runtime libraries anyways. i’m sure people can make it work.

    1. Thing is that C was more of an assembler than a true high-level language.
      It even has features to fiddle with memory in an obscure, assembler-ish way, making it useful for hardware-related programming (programming OSes, drivers). It’s not much unlike an assembler here (macro assembler).
      That’s why for C the term “super assembler” was coined once. Strictly speaking, C is no true programming language, maybe. It’s a set of standardized macros, rather.

      1. there’s no “true” high-level language, all of them exist on a gradient. C is generally lower level than many other languages. many early macro assemblers were called “high-level languages” by their designers.

        there’s nothing obscure in C’s memory access. that’s its strength and weakness. C++’s memory management obscures quite a bit (implicit ctor/dtor calls, for example), which is its strength and weakness too.

        and C is definitely a true programming language. so is assembler.

        and C is definitely not just a macro assembler. if you had any experience doing “high level programming” with actual macro assemblers (a very popular practice that continues to this day in some niches), you wouldn’t make that mistake of comparing it to C :)

        1. I’ve noticed when doing embedded projects with latest versions of gcc that the compiler sometimes pulls in stuff like malloc(), stdio stuff, exception handling for C++ (even when working in C). I had to spend half a day trying to figure out what the trigger was.

          1. I remember now, it was my use of setjmp()/longjmp() that pulled in all the C++ exception handling, even though the actual setjmp()/longjmp() did not refer to any of that.

          2. yeah i didn’t run into that because i’m avoiding all of the standard library for this project. but i ran into other things. gcc generates references to memcmp / memcpy / memset / memmove. which is pretty slick in some sense but it also means i have to supply a bit of the standard library, which i wasn’t expecting to do for this embedded project

        2. “and C is definitely a true programming language. so is assembler.”

          Depends on the point-of-view, I suppose. 🤷‍♂️
          We could count machine language (hex, binary) as a “language”, too.
          Or the BATCH scripting “language” in MS-DOS/COMMAND.COM..

  6. C++ is great for embedded. But I recommend you turn off RTTI and exceptions. And be very careful with the standard library as it can consume a lot of memory leading to stack-overflows. For instance the std regex implementation I used on an STM32 with FreeRTOS lead to stack overflows, even with large stack sizes, but a third party implementation was faster and used less memory.

    1. RTTI can be argued about whether it’s beneficial (it’s useful for some scenarios), but I’ll definitely agree that exceptions have no place in an embedded (MCU) system. RTTI mostly adds meta-information overhead, which can be an issue when you are limited in Flash and SRAM. Exceptions are however a waste of resources, and you’re right that much of the STL is a bit too heavy to use in constrained environments.

  7. @Malachi wow, you’re right, that syntax is gnarly! I’ve been dedicating what free time I have to learn it, and I consider myself a pretty decent hand at type systems, but I am just not getting it yet. The syntax really seems to obscure the intent, and every tutorial I’ve read so far is not very well written. Thanks for the tips on where to start and how to ensure backward compatibility!

  8. C++ is great also for embedded operating system like Android.

    Month ago, I started writing an Android game in C++ with NDK (Android Native Developer Kit) https://dir.by/developer/android/ndk/?lang=eng

    I really like C++ because I created objects in memory and don’t delete them.
    And if I delete that objects it is very fast.
    In Java or C# the garbage collector requires a lot of CPU time and I dont know when object will be finally deleted.

    But there is a small minus in C ++ is array sorting and searching. Need write algorithms myself.

  9. Hey everyone!

    Guess what? I was casually browsing the web the other day and stumbled upon an article that mentioned my own piece! Talk about a proud moment! 😄 I just had to share the excitement with all of you.

    I really hope you guys enjoyed reading my article as much as I enjoyed writing it. Your support and appreciation mean the world to me, seriously. Cheers

    Çağlayan

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.