It’s not uncommon to hear this statement: "C++ just doesn’t work well for embedded systems." Often, there will be vague, sweeping reasons such as:
- C code is going to be smaller than C++!
- C++ is totally going to blow up your stack / heap allocations!
- C++ is a powerful tool – and you have to be wary of shooting yourself in the foot! Those features can cause code bloat!
- C++ won’t work well if you are using an 8-bit microcontroller!
- Exceptions and RTTI make the output so much bigger and make things take longer!
I was also in the C-only camp – I thought that C++ would penalize me in size and speed. I was forced to confront my ignorance as we converted Pearl’s RearVision firmware from C to C++. I saw the power, convenience, and speed that C++ offered firsthand and the outcome assuaged my fears: the firmware binary is 1.6MB, even with RTTI and exceptions enabled in certain objects.
Here are some things that didn’t sink in until I started working with C++:
- If you are space/memory constrained, you can ruin yourself in the same way with C and C++. C is not inherently safe.
- C and C++ are different languages. You must understand your tools to apply them correctly.
- C++ was designed to be competitive in performance with C.
- There are many valuable features of C++ that do not cost you anything.
- You can use or selectively disable features you don’t need.
- RAII and SBRM are extremely helpful patterns that are easier to utilize in C++.
- Many teams recreate C++-like object models and vtables in C – the compiler can do it more efficiently than you can.
Your next question is likely "ok, fine – what’s so great about C++ then?" My short answer: C++ helps me write neater, more explicit, and less error-prone code than C does.
I’ve included a summary of C++ features that I utilize on a daily basis, grouped into two sets: those that help me write higher quality code and those that work well for embedded systems. Over the next few weeks I will be exploring these C++ features, including examples, tricks, and pitfalls to avoid.
In the meantime, if you’re looking for more information on C++, check out the Field Atlas Entry.
Code Quality
- Stronger type safety
- More explicit casting rules
nullptr
- Virtual functions and templated types allow me to eliminate
void *
usage - Passing arguments by reference can replace many error-prone uses of pass-by-pointer in C.
const
can allow you to pass-by-reference (eliminating a copy) but still indicate no modifications are allowed.
- Function overloading
- Enables neater code – interfaces that provide the same functionality but use different types can be grouped together, allowing for easier logical understanding of the functionality
auto
- C++ has verbose types. auto makes it so easy!
- Smart pointers
std::unique_ptr
andstd::shared_ptr
can eliminate many memory bugs that result from missing afree()
call
- Better utilization of scope-based resource management
- Take a lock, and ensure that a lock is released at all scope exit points automatically (never forget to
unlock()
beforereturn
!) - Ensure memory is freed properly at all scope exit points
- Take a lock, and ensure that a lock is released at all scope exit points automatically (never forget to
- Templates
- reduce repetition and generalize APIs, especially when differentiated primarily by type
- Stronger type-safety over C-style macros
- Enables more compile-time checking / computation
Embedded-Friendly Features
constexpr
- Indicate to the compiler that you intend for a constant/function to act as a compile-time constant
- Enables compiler optimization
std::align
,std::aligned_storage
,std::alignment_of
- std library interfaces for aligning memory
- Ability to customize allocators/deleters
- Works with smart pointers too – safely pass around memory without worrying about whether to call
free()
oraligned_free()
- Works with smart pointers too – safely pass around memory without worrying about whether to call
- Virtual functions/classes
- Templates
- Eliminate dead code – only use versions of a function that are required at compilation time (useful for safety-critical systems). Extra code is not ever generated.
std::mutex
- Provides a useful wrapper for your RTOS mutex
- Implementing
std::mutex
allows you to utilize SBRM and RAII type features such asstd::lock_guard
.
std::array
- Statically allocate memory, defined at compile time
Further Reading
- Field Atlas: C++
- Implementing std::mutex with FreeRTOS
- Ditch Your C-Style Pointers for Smart Pointers
- How to Use SBRM for C-Style Interfaces and Resources
- Migrating from C to C++:
NULL
vsnullptr
Using C++ Without the Heap
Want to use C++, but worried about how much it relies on dynamic memory allocations? Our course provides a hands-on approach for learning a diverse set of patterns, tools, and techniques for writing C++ code that never uses the heap.
Learn More on the Course Page
There are two tails I like to relate when I hear people say "C++ doesn’t work for embedded".
1:
For 12 years I coded for bare board embedded systems, first in assembler then, for the majority of the time, C.
Over the years I created a variety of techniques and code libraries to make the process easier and eliminate reinventing the wheel.
I was very pleased with what I had created!
Moving jobs, I was eventually tasked to look at how new applications could be created and I decided to look at C++.
I’d heard all of the FUD about C++ and was a fully signed up member of the "Embedded doesn’t need the complexities of C++" club.
As I started to read up about C++ I was hit by the sudden realisation of how much of C++ I had been re-inventing in C!
Objects, multiple instances, virtual functions, polymorphism, the lot!
And not only that, my C implementations were more verbose, error prone, and probably noware as efficient as the C++ compiler’s generated versions.
I’ve never perposefully written C for embedded since, unless there was a REALLY good reason not to use C++ (and there are very few good reasons).
2:
Before I really got to know the power of C++ fully, I had created an image library for our product line.
It used old school C pointers and navigated through the image using pointer arithmetic (+-1 for horizontal, +-line length for vertical, combination for diagional).
Eventually I rewrote the library and experimented with implementing image ‘iterators’.
The iterator class definitions had a heirarchy about four levels deep, so I was expecting that there could be a performance hit.
To my surprise (and pleasure) compiling the code, with optimisation enabled, showed the C++ code to have EXACTLY the same execution times of my old, clunky, pointer based library!
Hi John,
Thanks for sharing your stories. Your point #1 is the exact same realization we had on a project team when we switched from C to C++. I can’t tell you how many different implementations of objects and virtual tables in C I’ve seen over the years. In general, I guess it ties into our overall reluctance as programmers to buy a module instead of build it in-house. 🙂