Modern C++ in embedded systems – Part 1: Myth and Reality

In 1998, I wrote an article for Embedded Systems Programming called C++ in Embedded Systems – Myth and Reality. The article was intended to inform C programmers concerned about adopting C++ in embedded systems programming.

A lot has changed since 1998. Many of the myths have been dispelled, and C++ is used a lot more in embedded systems. There are many factors that may contribute to this, including more powerful processors, more challenging applications, and more familiarity with object-oriented languages.

C99 (an informal name for ISO/IEC 9899:1999) adopted some C++ features including const qualification and inline functions. C++ has also changed. C++11 and C++14 have added some cool features (how did I manage without the auto type specifier?) and some challenges, like deciding when to use constexpr functions.

But C++ has not displaced C, as I thought it would in 1998. C is alive and well in the Linux kernel, and there is a body of opinion implacably opposed to C++ in that environment.

The suspicion lingers that C++ is somehow unsuitable for use in small embedded systems. For 8- and 16-bit processors lacking a C++ compiler, that may be a concern, but there are now 32-bit microcontrollers available for under a dollar supported by mature C++ compilers. As this article series will make clear, with the continued improvements in the language most C++ features have no impact on code size or on speed. Others have a small impact that is generally worth paying for. To use C++ effectively in embedded systems, you need to be aware of what is going on at the machine code level, just as in C. Armed with that knowledge, the embedded systems programmer can produce code that is smaller, faster and safer than is possible without C++.

My history with C++
When I started a new microcontroller project a few years ago, I had to choose a tool-chain for the project. The MCU used (NXP LPC2458) was a 72MHz ARM7 with 512KB FLASH and 64KB RAM. Some toolchain vendors were surprised to be asked about the memory footprint of C++ libraries. When one vendor was pressed on the issue of a bloated library component, they said not many people are using C++ in such resource-constrained devices and it’s hard to justify the cost of improving the library. Bear in mind that this “resource-constrained device” was somewhat more powerful than the DOS platform that ran commercial software written in C++ in the 90s.

So in 2015, it seems that there’s still a need to de-mystify C++ for software engineers who are expert in embedded systems and in C, but wary of C++. If you’re not familiar with C++, if you find that not many people are using it for applications like yours and if it’s considered unsuitable for the Linux kernel, this wariness is understandable.

This is a revised version of the 1998 article addressing this issue. Less attention is given to features present in C99, since C programmers are likely to be familiar with them. The reader is assumed to be familiar with C99, which is used in the C code examples. The reader is also assumed to understand the C++ language features discussed, but doesn’t need to be a C++ expert. A reader that is unfamiliar with some language features can still get value from this article by skipping over those features. The intended use of C++ language features and why they might be preferable to alternatives is also beyond the scope of this article.

This article aims to provide a detailed understanding of what C++ code does at the machine code level, so that readers can evaluate for themselves the speed and size of C++ code as naturally as they do for C code.

To examine the nuts and bolts of C++ code generation, we will discuss the major features of the language and how they are implemented in practice. Implementations will be illustrated by showing pieces of C++ code followed by the equivalent (or near equivalent) C code. We will then discuss some pitfalls specific to embedded systems and how to avoid them.

We will not discuss the uses and subtleties of the C++ language or object-oriented design, as these topics have been well covered elsewhere. See http://en.cppreference.com/w/ for explanations of specific C++ language features.

C++11 and C++14 features are discussed separately in sections towards the end. The bulk of the article applies to the C++03 version of the language. C++11 is backward compatible with C++03 and C++14 is backward compatible with C++11. This helps the reader to ignore advanced features on a first reading and come back to them later.

Myths about C++. Some of the perceptions that discourage the use of C++ in embedded systems are:

  • C++ is slow.
  • C++ produces bloated machine code.
  • Objects are large.
  • Virtual functions are slow.
  • C++ isn’t ROMable.
  • Class libraries make large binaries.
  • Abstraction leads to inefficiency.

Most of these ideas are wrong. When the details of C++ code generation are examined in detail, hopefully it will be clear what the reality behind these myths is.

Anything C does, C++ can do.
One property of C++ is so obvious that it is often overlooked. This property is that C++ is almost exactly a superset of C. If you write a code fragment (or an entire source file) in the C subset, the compiler will usually act like a C compiler and the machine code generated will be what you would get from a C compiler. (See Compatibility of C and C++ for information about C constructs that won’t compile as C++)

Because of this simple fact, anything that can be done in C can also be done in C++. Existing C code can typically be re-compiled as C++ with about the same amount of difficulty that adopting a new C compiler entails. This also means that migrating to C++ can be done gradually, starting with C and working in new language features at your own pace. Although this is not the best way to reap the benefits of object-oriented design, it minimizes short term risk and provides a basis for iterative changes to a working system.

Front end features – a free lunch
Many of the features of C++ are strictly front-end issues. They have no effect on code generation. The benefits conferred by these features are therefore free of cost at runtime.

Default arguments to functions are an example of a cost-free front end feature. The compiler inserts default arguments to a function call where none are specified by the source.

A less obvious front end feature is ‘function name overloading’. Function name overloading is made possible by a remarkably simple compile time mechanism. The mechanism is commonly called ‘name mangling’, but has also been termed ‘name decoration’. Anyone who has seen a linker error about the absence of ?my_function@@YAHH@Z knows which term is more appropriate.

Name mangling modifies the label generated for a function using the types of the function arguments, or function signature. So a call to a function void my_function(int) generates a label like ?my_function@@YAXH@Z and a call to a function void my_function(my_class*) generates a label like ?my_function@@YAXPAUmy_class@@@Z . Name mangling ensures that functions are not called with the wrong argument types and it also allows the same name to be used for different functions provided their argument types are different.

Listing 1 shows a C++ code fragment with function name overloading. There are two functions called my_function , one taking an int argument, the other taking a char const* argument.

     // C++ function name overload example
     void my_function(int i) {
       // …
     }
    
     void my_function(char const* s) {
       // …
     }

     int main() {
        my_function(1);
        my_function(“Hello world”);
        return 0;
     }

Listing 1: Function name overloading

Listing 2 shows how this would be implemented in C. Function names are altered to add argument types, so that the two functions have different names.

     /* C substitute for function name overload */

     void my_function_int(int i) {
         /* … */
     }

     void my_function_charconststar(char const* s) {
         /* … */
     }
     int main() {
         my_function_int(1);
         my_function_charconststar (“Hello world”);
         return 0;
     }

Listing 2: Function name overloading in C

References
A reference in C++ is physically identical to a pointer. Only the syntax is different. References are safer than pointers because they can’t be null, they can’t be uninitialized, and they can’t be changed to point to something else. The closest thing to a reference in C is a const pointer. Note that this is not a pointer to a const value, but a pointer that can’t be modified. Listing 3 shows a C++ code fragment with a reference.

     // C++ reference example
     void accumulate(int& i, int j) {
         i += j;
     }

Listing 3: C++ reference

Listing 4 shows how this would be implemented in C.

     /* C substitute for reference example */
     void accumulate(int* const i_ptr, int j) {
         *i_ptr += j;
     }

Listing 4: Reference in C


Classes, member functions and objects

Classes and member functions are the most important new concept in C++. Unfortunately, they are usually introduced without explanation of how they are implemented, which tends to disorient C programmers from the start. In the subsequent struggle to come to terms with object-oriented design, hope of understanding code generation quickly recedes.

But a class is almost the same as a C struct . Indeed, in C++, a struct is defined to be a class whose members are public by default. A member function is a function that takes a pointer to an object of its class as an implicit parameter. So a C++ class with a member function is equivalent, in terms of code generation, to a C struct and a function that takes that struct as an argument.

Listing 5 shows a trivial class A with one member variable x and one member function f().

     // A trivial class

     class A {
     private:
         int x;
     public:
         void f();
     };

     void A::f() {
         x = 0;
     }

Listing 5: A trivial class with member function

Parts of a class are declared as private, protected, or public. This allows the programmer to prevent misuse of interfaces. There is no physical difference between private, protected, and public members. These specifiers allow the programmer to prevent misuse of data or interfaces through compiler enforced restrictions.

Listing 6 shows the C substitute for Listing 5. Struct A has the same member variable as class A and the member function A::f() is replaced with a function f_A(struct A*) . Note that the name of the argument of f_A(struct A*) has been chosen as “this”, which is a keyword in C++, but not in C. The choice is made deliberately to highlight the point that in C++, an object pointer named this is implicitly passed to a member function.

     /* C substitute for trivial class A */

     struct A {
         int x;
     };

     void f_A(struct A* this) {
         this->x = 0;
     }

Listing 6: C substitute for trivial class with member function

An object in C++ is simply a variable whose type is a C++ class. It corresponds to a variable in C whose type is a struct. A class is little more than the group of member functions that operate on objects belonging to the class. When an object-oriented application written in C++ is compiled, data is mostly made up of objects and code is mostly made up of class member functions.

Clearly, arranging code into classes and data into objects is a powerful organizing principle. Clearly also, dealing in classes and objects is inherently no less efficient than dealing with functions and data.Constructors and destructors
In C++, a constructor is a memberfunction that is guaranteed to be called when an object is instantiatedor created. This typically means that the compiler generates aconstructor call at the point where the object is declared. Similarly, adestructor is guaranteed to be called when an object goes out of scope.So a constructor typically contains any initialization that an objectneeds and a destructor does any tidying up needed when an object is nolonger needed.

The insertion of constructor and destructor callsby the compiler outside the control of the programmer is something thatmakes the C programmer uneasy at first. Indeed, programming practices toavoid excessive creation and destruction of so-called temporary objectsare a preoccupation of C++ programmers in general. However, theguarantee that constructors and destructors provide – that objects arealways initialized and are always tidied up – is generally worth thesacrifice. In C, where no such guarantees are provided, consequencesinclude frequent initialization bugs and resource leakage.

Namespaces
C++namespaces allow the same name to be used in different contexts. Thecompiler adds the namespace to the definition and to name references atcompile time. This means that names don’t have to be unique in theapplication, just in the namespace in which they are declared. Thismeans that we can use short, descriptive names for functions, globalvariables, classes, etc. without having to keep them unique in the entreapplication. Listing 7 shows an example using the same function name in two namespaces.

     // Namespace example
     namespace n1 {
         void f() {
         }
         void g() {
             f(); // Calls n1::f() implicitly
         }
     };

     namespace n2 {
         void f() {
         }
         void g() {
             f(); // Calls n2::f() implicitly
         }
     };

     int main() {
         n1::f();
         n2::f();
         return 0;
     }

Listing 7: Namespaces

Whenlarge applications are written in C, which lacks namespaces, this isoften achieved by adding prefixes to names to ensure uniqueness. See Listing 8.

     /* C substitute for namespace */

     void n1_f() {
     }

     void n1_g() {
         n1_f();
     }

     void n2_f() {
     }

     void n2_g() {
         n2_f();
     }

     int main() {
         n1_f();
         n2_f();
         return 0;
     }

Listing 8: C substitute for namespaces using prefixes

Inline functions
Inlinefunctions are available in C99, but tend to be used more in C++ becausethey help achieve abstraction without a performance penalty.

Indiscriminateuse of inline functions can lead to bloated code. Novice C++programmers are often cautioned on this point, but appropriate use ofinline functions can significantly improve both size and speed.

Toestimate the code size impact of an inline function, estimate how manybytes of code it takes to implement it and compare that to the number ofbytes needed to do the corresponding function call. Also consider thatcompiler optimization can tilt the balance dramatically in favor of theinline function. If you conduct actual comparisons studying generatedcode with optimization turned on, you may be surprised by how complex aninline function can profitably be. The breakeven point is often farbeyond what can be expressed in a legible C macro.

Operator overloading

AC++ compiler substitutes a function call when it encounters anoverloaded operator in the source. Operators can be overloaded withmember functions or with regular, global functions. So the expression x+y results in a call to operator+(x, y) or x.operator+(y) if one of these is declared. Operator overloading is a front end issueand can be viewed as a function call for the purposes of codegeneration.

New and delete
In C++, new and delete do the same job as malloc() and free() in C, except that they add constructor and destructor calls, eliminating a source of bugs.

Simplified container class

Toillustrate the implementation of a class with the features we havediscussed, let us consider an example of a simplified C++ class and its Calternative.

Listing 9 shows a (not very useful)container class for integers featuring a constructor and destructor,operator overloading, new and delete. It makes a copy of an int arrayand provides access to array values using the operator[] , returning 0 for an out of bounds index. It uses the (nothrow) variant of new to make it easier to compare to the C alternative.

     #include
     #include

     class int_container {
     public:
         int_container(int const* data_in, unsigned len_in) {
             data = new(std::nothrow) int[len_in];
             len = data == 0? 0: len_in;
             for (unsigned n = 0; n < len; ++n)
                 data[n] = data_in[n];
         }
 
         ~int_container() {
             delete [] data;
         }
 
         int operator[](int index) const {
             return index >= 0 && ((unsigned)index) < len? data[index]: 0;
         }
     private:
         int* data;
         unsigned len;
     };

     int main() {
         int my_data[4] = {0, 1, 2, 3};
         int_container container(my_data, 4);
         std::cout << container[2] << "n";
     }

Listing 9: A simple integer container class featuring constructor, destructor, operator overloading, new and delete

Listing 10 is a C substitute for the class in Listing 9. Operator overload int_container::operator[](int) is replaced with function int_container_value(…) . The constructor and destructor are replaced with int_container_create(…) and int_container_destroy(…) . These must be called by the user of the class, rather than calls being added automatically by the compiler.

     #include
     #include

     struct int_container {
         int* data;
         unsigned len;
     };

     void int_container_create(struct int_container* this, int     const* data_in, unsigned len_in) {
         this->data = malloc(len_in * sizeof(int));
         this->len = this->data == 0? 0: len_in;
         for (unsigned n = 0; n < len_in; ++n)
             this->data[n] = data_in[n];
     }

     void int_container_destroy(struct int_container* this) {
         free(this->data);
     }

     int int_container_value(struct int_container const* this, int index) {
         return index >= 0 && index < this->len? this->data[index]:  0;
     }

     int main() {
       int my_data[4] = {0, 1, 2, 3};
       struct int_container container;
       int_container_create(&container, my_data, 4);
       printf(“%dn”, int_container_value(&container, 2));
       int_container_destroy(&container);
     }

Listing 10: C substitute for simple string class

Note how much easier to read main() is in Listing 9 than in Listing 10. It is also safer, more coherent,more maintainable, and just as fast. Consider which version of main() is more likely to contain bugs. Consider how much bigger the differencewould be for a more realistic container class. This is why C++ and theobject paradigm are safer than C and the procedural paradigm forpartitioning applications.

All C++ features so far discussed confer substantial benefits at no runtime cost.

Inheritance
Indiscussing how C++ implements inheritance, we will limit our discussionto the simple case of single, non-virtual inheritance. Multipleinheritance and virtual inheritance are more complex and their use israre by comparison.

Let us consider the case where class Binherits from class A. (We can also say that B is derived from A or thatA is a base class of B.)

We know from the previous discussionwhat the internal structure of an A is. But what is the internalstructure of a B? We learn in object-oriented design (OOD) thatinheritance models an ‘is a’ relationship – that we should useinheritance when we can say that a B ‘is a’ A. So if we inherit Circlefrom Shape, we’re probably on the right track, but if we inherit Shapefrom Color, there’s something wrong.

What we don’t usually learnin OOD is that the ‘is a’ relationship in C++ has a physical as well asa conceptual basis. In C++, an object of derived class B is made up ofan object of base class A, with the member data of B tacked on at theend. The result is the same as if the B contains an A as its firstmember. So any pointer to a B is also a pointer to an A. Any memberfunctions of class A called on an object of class B will work properly.When an object of class B is constructed, the class A constructor iscalled before the class B constructor and the reverse happens withdestructors.

Listing 11 shows an example of inheritance. Class B inherits from class A and adds the member function B::g() and the member variable B::secondValue.

     // Simple example of inheritance

     class A {
     public:
         A();
         int f();
     private:
         int value;
     };

     A::A() {
         value = 1;
     }

     int A::f() {
         return value;
     }

     class B: public A {
     private:
         int secondValue;
     public:
         B();
         int g();
     };

     B::B() {
         secondValue = 2;
     }

     int B::g() {
         return secondValue;
     }

     int main() {
         B b;
         b.f();
         b.g();
         return 0;
     }

Listing 11: Inheritance

Listing 12 shows how this would be achieved in C. Struct B contains a struct A as its first member, to which it adds a variable secondValue . The function BConstructor(struct B*) calls AConstructor to ensure initialization of its ‘base class’. Where the function main() calls b.f() in Listing 11, f_A(struct A*) is called in Listing 12 with a cast.

     /* C Substitute for inheritance */

     struct A {
         int value;
     };

     void AConstructor(struct A* this) {
         this->value = 1;
     }

     int f_A(struct A* this) {
         return this->value;
     }

     struct B {
         struct A a;
         int secondValue;
     };

     void BConstructor(struct B* this) {
         AConstructor(&this->a);
         this->secondValue = 2;
     }

     int g_B(struct B* this) {
         return this->secondValue;
     }

     int main() {
         struct B b;
         BConstructor(&b);
         f_A ((struct A*)&b);
         g_B (&b);
         return 0;
     }

Listing 12: C Substitute for inheritance

Itis startling to discover that the rather abstract concept ofinheritance corresponds to such a straightforward mechanism. The resultis that well-designed inheritance relationships have no runtime cost interms of size or speed.

Inappropriate inheritance, however, canmake objects larger than necessary. This can arise in class hierarchies,where a typical class has several layers of base class, each with itsown member variables, possibly with redundant information.

Virtual functions
Virtualmember functions allow us to derive class B from class A and override avirtual member function of A with one in B and have the new functioncalled by code that knows only about class A. Virtual member functionsprovide polymorphism, which is a key feature of object-oriented design.

Aclass with at least one virtual function is referred to as a‘polymorphic’ class. The distinction between a polymorphic and anon-polymorphic class is significant because they have differenttrade-offs in runtime cost and functionality.

Virtual functionshave been controversial because they exact a price for the benefit ofpolymorphism. Let us see, then, how they work and what the price is.

Virtualfunctions are implemented using an array of function pointers, called avtable, for each class that has virtual functions. Each object of such aclass contains a pointer to that class’s vtable. This pointer is putthere by the compiler and is used by the generated code, but it is notavailable to the programmer and it cannot be referred to in the sourcecode. But inspecting an object with a low level debugger will reveal thevtable pointer.

When a virtual member function is called on anobject, the generated code uses the object’s vtable pointer to accessthe vtable for that class and extract the correct function pointer. Thatpointer is then called.

Listing 13 shows an example usingvirtual member functions. Class A has a virtual member function f(),which is overridden in class B. Class A has a constructor and a membervariable, which are actually redundant, but are included to show whathappens to vtables during object construction.

     // Classes with virtual functions

     class A {
     private:
         int value;
     public:
         A();
         virtual int f();
     };

     A::A() {
         value = 0;
     }

     int A::f() {
         return 0;
     }

     class B: public A {
     public:
         B();
         virtual int f();
     };

     B::B() {
     }

     int B::f() {
         return 1;
     }

     int main() {
         B b;
         A* aPtr = &b;
         aPtr->f();
         return 0;
     }

Listing 13: Virtual Functions

Listing 14 shows what a C substitute would look like. The second last line in main() is a dangerous combination of casting and function pointer usage.

     /* C substitute for virtual functions */

     struct A {
         void **vTable;
         int value;
     };

     int f_A(struct A* this);

     void* vTable_A[] = {
         (void*) &f_A
     };

     void AConstructor(struct A* this) {
         this->vTable = vTable_A;
         this->value = 1;
     }

     int f_A(struct A* this) {
         return 0;
     }

     struct B {
         struct A a;
     };

     int f_B(struct B* this);

     void* vTable_B[] = {
         (void*) &f_B
     };

     void BConstructor(struct B* this) {
         AConstructor((struct A*) this);
         this->a.vTable = vTable_B;
     }

     int f_B(struct B* this) {
         return 1;
     }

     int main() {
         struct B b;
         struct A* aPtr;
    
         BConstructor(&b);
         typedef void (*f_A_Type)(struct A*);

         aPtr = (struct A*) &b;
         ((f_A_Type)aPtr->vTable[0]) (aPtr);
         return 0;
     }

Listing 14: C substitute for virtual functions

This is the first language feature we have seen that entails a runtime cost. So let us quantify the costs of virtual functions.

Thefirst cost is that it makes objects bigger. Every object of a classwith virtual member functions contains a vtable pointer. So each objectis one pointer bigger than it would be otherwise. If a class inheritsfrom a class that already has virtual functions, the objects alreadycontain vtable pointers, so there is no additional cost. But adding avirtual function can have a disproportionate effect on a small object.An object can be as small as one byte and if a virtual function is addedand the compiler enforces four-byte alignment, the size of the objectbecomes eight bytes. But for objects that contain a few membervariables, the cost in size of a vtable pointer is marginal.

Thesecond cost of using virtual functions is the one that generates mostcontroversy. That is the cost of the vtable lookup for a function call,rather than a direct one. The cost is a memory read before every call toa virtual function (to get the object’s vtable pointer) and a secondmemory read (to get the function pointer from the vtable). This cost hasbeen the subject of heated debate and it is hard to believe that thecost is typically less than that of adding an extra parameter to afunction. We hear no arguments about the performance impact ofadditional function arguments because it is generally unimportant, justas the cost of a virtual function call is generally unimportant.

Aless discussed, but more significant, cost of virtual functions istheir impact on code size. When an application is linked aftercompilation, the linker can identify regular, non-virtual functions thatare never called and remove them from the memory footprint. But becauseeach class with virtual functions has a vtable containing pointers toall its virtual functions, the pointers in this vtable must be resolvedby the linker. This means that all virtual functions of all classes usedin a system are linked. Therefore, if a virtual function is added to aclass, the chances are that it will be linked, even if it is nevercalled.

So virtual functions have little impact on speed, buttheir effects on code size and data size should be considered. Becausethey involve overheads, virtual functions are not mandatory in C++ asthey are in other object-oriented languages. So if, for a given class,you find the costs outweigh the benefits, you can choose not to usevirtual functions.

Templates

C++ templates arepowerful, as shown by their use in the Standard C++ Library. A classtemplate is rather like a macro that produces an entire class as itsexpansion. Because a class can be produced from a single statement ofsource code, careless use of templates can have a devastating effect oncode size. Older compilers will expand a templated class every time itis encountered, producing a different expansion of the class in eachsource file where it is used. Newer compilers and linkers, however, findduplicates and produce at most one expansion of a given template with agiven parameter class.

Used appropriately, templates can save alot of effort at little or no cost. After all, it’s a lot easier andprobably more efficient to use complex from the StandardC++ Library, rather than write your own class.

Listing 15 shows asimple template class A. An object of class A has amember variable of type T, a constructor to initialize and a memberfunction A::f() to retrieve it.

     // Sample template class

     template class A {
     private:
         T value;
     public:
         A(T);
         T f();
     };

     template A::A(T initial) {
         value = initial;
     }

     template T A::f() {
         return value;
     }

     int main() {
         A a(1);
         a.f();
         return 0;
     }

Listing 15: A C++ template

The macro A(T) in Listing 16 approximates a template class in C. It expands to a struct declaration and function definitions for functions corresponding to theconstructor and the member function. We can see that although it ispossible to approximate templates in C, it is impractical for anysignificant functionality.

     /* C approximation of template class */

     #define A(T)                                              
         struct A_##T {                                        
             T value;                                          
         };                                                    
                                                               
         void AConstructor_##T(struct A_##T* this, T initial) {
             (this)->value = initial;                          
         }                                                     
                                                               
         T A_f_##T(struct A_##T* this) {                       
             return (this)->value;                             
         }
    
     A(int) /* Macro expands to ‘class’ A_int */
    
     int main() {
         struct A_int a;
         AConstructor_int(&a, 1);
         A_f_int(&a);
         return 0;
     }

Listing 16: A C ‘template’

Exceptions
Exceptions are to setjmp() and longjmp() what structured programming is to goto . They impose strong typing, guarantee that destructors are called, and prevent jumping to a disused stack frame.

Exceptionsare intended to handle conditions that do not normally occur, soimplementations are tailored to optimize performance for the case whereno exceptions are thrown. With modern compilers, exception supportresults in no runtime cost unless an exception is thrown. The time takento throw an exception is unpredictable and may be long due to twofactors. The first is that the emphasis on performance in the normalcase is at the expense of performance in the abnormal case. The secondfactor is the runtime of destructor calls between an exception beingthrown and being caught.

The use of exceptions also causes a setof tables to be added to the memory footprint. These tables are used tocontrol the calling of destructors and entry to the correct catch blockwhen the exception is thrown.

For detailed information on the costs of exceptions with different compilers, see Effective C++ in an Embedded Environment.

Becauseof the cost of exception support, some compilers have a ‘no exceptions’option, which eliminates exception support and its associated costs.

     // C++ Exception example

     #include
     using namespace std;

     int factorial(int n) throw(const char*) {
         if (n<0)
             throw “Negative Argument to factorial”;
         if (n>0)
             return n*factorial(n-1);
         return 1;
     }

     int main() {
        try {
              int n = factorial(10);
              cout << "factorial(10)=" << n;
         } catch (const char* s) {
              cout << "factorial threw exception: " << s << "n";
         }
         return 0;
     }

Listing 17: A C++ exception example

Listing 17 above shows an example of an exception and Listing 18 below shows a C substitute that has several shortcomings. It uses global variables. It allows longjmp(ConstCharStarException) to be called either before it is initialized by setjmp(ConstCharStarException) or after main() has returned. In addition, substitutes for destructor calls must be done by the programmer before a longjmp() . There is no mechanism to ensure that these calls are made.

     /* C approximation of exception handling */

     #include
     #include

     jmp_buf ConstCharStarException;
     const char* ConstCharStarExceptionValue;

     int factorial(int n) {
         if (n<0) {
         ConstCharStarExceptionValue = “Negative Argument to factorial”;
             longjmp(ConstCharStarException, 0);
         }
         if (n>0)
         return n*factorial(n-1);
         return 1;
     }

     int main() {
         if (setjmp(ConstCharStarException)==0) {
             int n = factorial(10);
             printf(“factorial(10)=%d”, n);
         } else {
             printf(“factorial threw exception: %sn”,     ConstCharStarExceptionValue);
         }
         return 0;
     }

Listing 18: A C ‘exception’ example

Forlanguage features discussed up to this point, it has been possible toentertain the possibility of a C substitute as a practical proposition.In this case of exceptions, however, the additional complexity andopportunities for error make a C substitute impractical. So if you’rewriting C, the merits of exception-safe programming are a moot point.

Runtime type information
Theterm ‘runtime type information’ suggests an association with purerobject-oriented languages like Smalltalk. This association raisesconcerns that efficiency may be compromised. This is not so. The runtimecost is limited to the addition of a type_info object for eachpolymorphic class and type_info objects aren’t large.

To measure the memory footprint of a type_info object, the code in Listing 19 was compiled to assembly. The output was put through the name demanglerat www.demangler.com and the result was annotated to highlight the sizeof the type_info object and the class name string. The result was 30bytes. This is about the cost of adding a one line member function toeach class.

Many compilers have an option to disable runtime typeinformation, which avoids this cost for an application that does notuse type_info objects.

     // type_info test classes /////////////////
     class Base {
     public:
       virtual ~Base() {}
     };
     class Derived: public Base {};
     class MoreDerived: public Derived {};

/* type_info for more_derived generated by g++ 4.5.3 for cygwin. Total 30 bytes */
_typeinfo for MoreDerived:
    .long    _vtable for __cxxabiv1::__si_class_type_info 8        /* 4 bytes */
    .long    _typeinfo name for MoreDerived                        /* 4 bytes */
    .long    _typeinfo for Derived                                 /* 4 bytes */
/* … */
_vtable for MoreDerived:
    .long    0                                                
    .long    _typeinfo for MoreDerived                             /* 4 bytes */
    .long    _MoreDerived::~MoreDerived()                  
    .long    _MoreDerived::~MoreDerived()                   
/* … */
_typeinfo name for MoreDerived:
    .ascii “11MoreDerived”                                     /*14 bytes */

Listing 19: type_info Memory Footprint Measurement

Part 2: Modern C++ in Embedded Systems: Evaluating C++


Dominic Herity
is a Principal Software Engineer at Faz Technology Ltd. He has 30 years’ experience writing software for platforms from 8 bitto 64 bit with full life cycle experience in several commerciallysuccessful products. He served as Technology Leader with Silicon &Software Systems and Task Group Chair in the Network Processing Forum.He has contributed to research into Distributed Operating Systems andHigh Availability at Trinity College Dublin. He has publications onvarious aspects of Embedded Systems design and has presented at severalconferences in Europe and North America. He can be contacted at .

26 thoughts on “Modern C++ in embedded systems – Part 1: Myth and Reality

  1. “Out of curiosity, what would be your opinion on working with peripheral functions? From my experience, singleton classes are a fair choice, but seem to be an inappropriate when you have multiple iterations of the same peripheral e.g. a timer function.”

    Log in to Reply
  2. “I'd like to add one disadvantage of virtual functions: they are an inbline & optimization barrier. This can make a hughe difference in performance (in addition to code size) for functions at or near the edges of the call tree. when the object relations ar

    Log in to Reply
  3. “This is true. Thanks for pointing it out. I was comparing a virtual function to a function in a separate translation unit that the compiler can't access for optimisation, but this can't be assumed.”

    Log in to Reply
  4. “Why I will never use c++ for small mcus:n1. Too often, it is assumed that a highly skilled C engineer can just start using C++ as a better “C”. Not true – C++ has a learning curve far beyond “C”. I dare say many C++ engineers do not fully understand

    Log in to Reply
  5. “I don't just assume that C++ can be used as a better C. I assert it.nTake your example of malloc versus new. I don't agree that malloc if preferable, but there's nothing to stop you using it in C++, if that's what you really want.nAnything you can do in

    Log in to Reply
  6. “It's not quite true that C++ is a superset of C. Many of the things are violate the superset idea are good (stricter type checking for example).nnI agree that C++ does not always result in larger compiled code. However, final implementation are often bi

    Log in to Reply
  7. “All true. My motivation for the original 1998 article was to challenge the often stated proposition that C++ is _inherently_ unsuitable for embedded systems. It was aimed at C programmers who were thinking about trying it, but deterred by inaccurate asser

    Log in to Reply
  8. “I think a lot of C fans are missing the point. If you write C++ code like you would write C code there is no difference. If you write C++ code like your were writing code for a desktop then it will be slow and huge. C++ brings new tools and new tools brin

    Log in to Reply
  9. “Building on the idea in “Objects? No Thanks” policy based class design in general allows “open closed” idiom with zero overhead. A well designed C++ library can have a fraction of the flash footprint of its C counterpart.”

    Log in to Reply
  10. “Here my rebuttal of the SO post for embedded specific:n[1] embedded programs are smaller which mitigates the problem, model the singleton as a template class and you can always pass the class object around (its empty anyway the optimizer will remove it i

    Log in to Reply
  11. “Yeah… This whole mythology surrounding C++ in embedded will probably never change. But I'm also trying to fight this myth by doing something that many people will consider a complete nonsense – I'm writing an RTOS for ARM Cortex-M microcontrollers in C+

    Log in to Reply
  12. “Hello Dominic and thanks for your reply!nnPersonally I find the importance of size to be a bit overstated. Don't get me wrong – I don't say that it's OK for the RTOS to use 1MB of flash and 256kB of RAM just to blink a LED – I'm far from that. But at th

    Log in to Reply
  13. “Great post! We also provide embedded systems services in North America within 25 years of experience. I really appreciate you on this topic because most of people have these myths about c++ embedded systems that is not true and it is very difficult to ch

    Log in to Reply
  14. “Based on this and some other internet reading, I decided to see where C++ would go. “Hello world” fails on MSP430 as the iostream requires eyewatering 128kB of FRAM to compile. Not very auspicious beginning indeed. “

    Log in to Reply
  15. “I had a similar experience with iostreams. I resolved it by writing a lightweight substitute that only does the basics – no locales, formatting, etc. The good news was that the rest of the standard C++ library didn't cause excessive bloat.”

    Log in to Reply
  16. “Like many others, Iu2019m always interest to learn more about the relative merits of different languages for firmware development.nnOne thing that struck me reading towards the end of this argument, is about the reference to the size of code… once it

    Log in to Reply
  17. “In many aspects that actually matter to C programmers, C++ is exactly NOT a superset of the two languages. Designated initializers are one example where C++ programmers like you, Dominic, are too ignorant to realize how valuable they are for example in us

    Log in to Reply
  18. “I said that “C++ is _almost_ exactly a superset of C”. I don't know any important feature in C that C++ lacks, but there is a lot in C++ that C lacks. It seems to me that designated initializers make it a bit easier to do something you shouldn't be doin

    Log in to Reply
  19. “I've been using C++ exclusively for some years for all work on ARM Cortex-M devices with a variety of tool chains, both bare metal and with an RTOS. I'd use it for more limited devices too, but the opportunity has not arisen. I have found that C++ is infi

    Log in to Reply

Leave a Reply

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