C++
C++ in Embedded Systems
Every chapter in this domain is real, useful C++ — but on a microcontroller with a few KB of RAM and hard real-time deadlines, not every feature is a good fit. This closing chapter is the honest map: what a typical bare-metal C++ codebase avoids from everything covered so far, what it happily keeps, and why — closing the loop back to Embedded C, which covers the same hardware-constrained mindset for plain C.
- What it is
- The subset of C++ commonly used on bare-metal/RTOS targets, and the specific features commonly avoided or restricted, with concrete reasons for each.
- Mental model
- Desktop C++ optimizes for programmer productivity at some cost in predictability; embedded C++ makes the opposite trade — predictable timing and memory footprint, even at some cost to convenience.
- Key numbers
- Exception-handling machinery alone can add tens of KB to a binary — often a meaningful fraction of a microcontroller's entire flash budget.
- Reach for it when
- Deciding, up front, which C++ features a new embedded project's style guide should allow — a decision worth making deliberately, not by accident.
Why exceptions are often disabled
Exception Handling covered the mechanism; here's why
many embedded teams compile with exceptions turned off entirely (-fno-exceptions on GCC):
- Code size — the exception-handling tables and unwinding machinery the compiler generates add real, often significant flash usage, even in code paths that never actually throw.
- Unpredictable worst-case timing — stack unwinding's cost depends on how deep the call stack is at the moment of the throw — hard to bound tightly, a problem for hard real-time deadlines.
- No OS to catch a truly uncaught exception gracefully — on a desktop, an uncaught exception terminates one process; on bare metal, it can mean the entire device.
The common replacement: return codes or std::optional-style "might not have a value"
types, checked explicitly — closer to
C's own error-handling convention, chosen deliberately for the same
predictability reasons.
Why heavy dynamic allocation is limited
STL containers and
smart pointers both lean on heap allocation — exactly
the concern Embedded C's Dynamic Memory raises about
malloc, still true here:
- Heap fragmentation over a device's operating lifetime, with no OS-level memory compaction to fall back on.
- Allocation failure with nowhere sensible to report it, if exceptions are also disabled.
- Non-deterministic allocation timing, a problem for the same hard-real-time reasons as exceptions.
Common replacements: fixed-size containers sized at compile time, memory pools with a bounded number of
fixed-size blocks, and std::array (a stack-allocated, fixed-size STL type not covered
earlier precisely because it has none of these issues) in place of std::vector.
RTTI — usually disabled too
RTTI (Run-Time Type Information) is what powers dynamic_cast and
typeid — asking "what concrete type is actually behind this base-class pointer" at runtime.
Like exceptions, it adds real code size and is very commonly disabled
(-fno-rtti) on constrained targets. The virtual
function dispatch that makes polymorphism work does not depend on RTTI at all — that
part keeps working fine with RTTI turned off.
What carries over cleanly
Most of this domain isn't actually the problem — the features above are a short, specific list, not "avoid C++." What's commonly kept, freely:
- Classes & encapsulation
- Zero runtime cost — a class compiles down to essentially the same code as a struct plus plain functions.
- RAII
- Genuinely valuable on embedded specifically — automatic peripheral setup/teardown with no runtime cost.
- Virtual functions
- A small, predictable, constant-time cost (one extra pointer indirection) — commonly used for driver interfaces.
- Templates
- Zero runtime cost, since everything resolves at compile time — genuinely popular for embedded, though watch total code size across many instantiations.
std::unique_ptr- Zero overhead versus a raw pointer — commonly allowed even where
shared_ptrand heavy STL containers are not.
Real-world applications
- Automotive and aerospace firmware — MISRA C++ is a widely-used coding standard restricting exactly this kind of feature subset, for the safety-critical predictability reasons above.
- RTOS-based embedded C++ projects — commonly compile with exceptions and RTTI disabled from the project template onward, as a deliberate, upfront decision rather than a later retrofit.
- Game console and real-time graphics code — historically avoids exceptions and heavy STL allocation for the same hard-real-time and predictable-footprint reasons as embedded firmware.
Gotchas
- Disabling exceptions doesn't remove every exception-related code path automatically — some standard library functions still assume exceptions are enabled internally; check your specific toolchain's embedded/freestanding documentation rather than assuming.
- This restricted subset is a team/project decision, not a law of physics — a Linux-class embedded system (an SBC running embedded Linux, for instance) often has enough memory and OS support to use far more of standard C++ freely; the constraints above scale with how tight the actual hardware budget is.
- "We don't use exceptions" is a project convention that needs enforcing (compiler flags, code review, static analysis) — nothing stops a contributor from accidentally writing a
throwotherwise. - This chapter closes the C++ domain, but not the notebook's language coverage — the upcoming Go domain covers a different, modern take on some of the same tradeoffs this chapter discusses (garbage collection instead of manual/RAII memory management, no exceptions at all by design).