You are on page 1of 31

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

February 17, 2015 Dominic Herity


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++

1
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.

2
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() {

3
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;
}

4
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

5
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

6
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;

7
}

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};

8
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;
}

9
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;
};

10
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.

11
// 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
};

12
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.

13
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;
}

14
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

15
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;

16
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:

17
.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

Modern C++ embedded systems – Part 2: Evaluating C++


February 17, 2015 Embedded Staff
Having discussed the implementation of the main C++ language features in Part 1 of this
series, we can now evaluate C++ in terms of the machine code it generates. Embedded
system programmers are particularly concerned about code and data size; we need to
discuss C++ in these terms.
How big is a class? In C++, most code is in class member functions and most data is in
objects belonging to these classes. C++ classes tend to have many more member
functions than a C programmer would expect to use. This is because well-designed classes
are complete and contain member functions to do anything with objects belonging to the
class that might legitimately be needed. For a well-conceptualized class, this number will
be reasonably small, but nevertheless larger than what the C programmer is accustomed
to.
When calculating code size, bear in mind that modern linkers can extract from object files
only those functions that are actually called, not the entire object files. In essence, they
treat each object file like a library. This means that unused non-virtual class member
functions have no code size penalty. So a class that seems to have a lot of baggage in
terms of unused member functions may be quite economical in practice.
Although class completeness need not cause code bloat for non-virtual functions, it is
reasonable to assume that all virtual functions of all classes used in a system will be linked
into the binary.
How big is an object? The size of an object can be calculated by examining its class (and
all its base classes). Ignore member functions and treat the data the same as for a struct.
Then add the size of a pointer if there are any virtual functions in the class or base classes.
You can confirm your result by using the sizeof operator. It will become apparent that the
combined size of objects in a system need be no greater than the size of data in a C-based
procedural model. This is because the same amount of state is needed to model a system
regardless of whether it is organized into objects.
C++ and the heap
Heap usage is much more common in C++ than in C. This is because of encapsulation. In C,
18
where a function requires an unknown amount of memory, it is common to externalize the
memory as an input parameter and leave the caller with the problem. This is at least safer
than mallocing an area and relying on the user to free it. But C++, with its encapsulation
and destructors, gives class designers the possibility (and responsibility) of managing the
memory used by objects of that class.
This difference in philosophy is evident in the difference between C strings and a C++
string class. In C, you get a char array. You have to decide in advance how long your string
can be and you have to continuously make sure it doesn’t get any bigger. A C++ string
class, however, uses ‘new’ and ‘delete’ to allow a string to be any size and to grow if
necessary. It also makes sure that the heap is restored when the string is destroyed.
The consequence of all this is that you can scrape by in an embedded system written in C
without using ‘malloc’ and ‘free’, but avoiding ‘new’ and ‘delete’ in C++ is a much bigger
sacrifice.
The main reason for banning heap usage in an embedded application is the threat of heap
fragmentation. As the software runs, memory allocations of different sizes are acquired
and released. The situation can arise where many small allocations are scattered through
the heap and, although a large fraction of the heap may be available for use, it is all in
small fragments and it is not possible to provide an allocation bigger that the largest
fragment.
In an embedded system that runs continuously for years, heap fragmentation may occur
only under certain conditions a long time after deployment, and may have been missed in
test coverage.
Heap fragmentation can be avoided by using a non-fragmenting allocator. One solution is
to re-implement operator ‘new’ and operator ‘delete’ using a collection of fixed-size buffer
pools. Operator ‘new’ returns the smallest available buffer that will satisfy the request.
Since buffers are never split, fragmentation (or external fragmentation to be precise) does
not occur. Disadvantages of this technique are that it uses more memory and that it must
be configured to provide the right number of the right sized buffers.
Another alternative to banning heap usage outright is to allow it during initialization, but
ban it once the system is running. This way, STL containers and other objects that use the
heap can be modified and configured during initialization, but must not be modified once
initialization is complete. With this policy, we know that if the system starts up, it won’t
run out of memory no matter how long it runs. For some applications, this level of
flexibility is enough.
ROMable objects
Linkers for embedded systems allow const static data to be kept in ROM. For a system
written in C, this means that all the non-varying data known at compile time can be
specified by static initializers, compiled to be stored in ROM and left there.
In C++, we can do the same, but we tend not to. In well-designed C++ code, most data is
encapsulated in objects. Objects belong to classes and most classes have constructors.
The natural object-oriented equivalent to const initialized data is a const object.
A const static object that has a constructor must be stored in RAM for its constructor
to initialize it. So where in C a const static object occupies cheap and plentiful ROM, its
natural heir in C++ occupies expensive and scarce RAM. Initialization is performed by start-

19
up code that calls static constructors with parameters specified in declarations. This start-
up code occupies more ROM than the static initializer would have.
So if a system includes a lot of data that can be kept in ROM, special attention to class
design is needed to ensure that the relevant objects are ROMable. For an object to be
ROMable, it must be capable of initialization by a static initializer like a
C struct. Although the easy way to do this is to make it a simple C struct (without
member functions), it is possible to make such a class a bit more object-oriented.
The criteria for a static initializer to be allowed for a class are:
 The class must have no base classes.
 It must have no constructor.
 It must have no virtual functions.
 It must have no private or protected members.
 Any classes it contains must obey the same rules.
In addition, we should also require that all member functions of a ROMable class be const.
A C struct meets these criteria, but so does a class that has member functions.
Although this solves the ROMability problem and enhances the C struct with member
functions, it falls far short of the object-oriented ideal of a class that is easy to use
correctly and difficult to use incorrectly. The unwary class user can, for example, declare
and use a non-const, uninitialized instance of the class that is beyond the
control of the class designer.
To let us sleep securely in our object-oriented beds, something more is needed. That
something is ‘class nesting’. In C++, we can declare classes within classes. We can take our
dubious class that is open to misuse and put it in the private segment of another class. We
can also make the const static instances of the dubious class private static members of the
encapsulating class. This outer class is subject to none of the restrictions that the
ROMable class is, so we can put in it a proper restricted interface to our const static data.
To illustrate this discussion, let us consider a simplified example of a handheld electronic
multi-language dictionary. To keep it simple, the translator translates from English to
German or French and it has a vocabulary of two words, ‘yes’ and ‘no’. Obviously, these
dictionaries must be held on ROM. A C solution would be something like Listing 20 .
/* A C ROMable dictionary */
#include
typedef struct {
const char* englishWord;
const char* foreignWord;
} DictEntry;
const static DictEntry germanDict[] = {
{“yes”, “ja”},
{“no”, “nein”},
{NULL, NULL}
};
const static DictEntry frenchDict[] = {
{“yes”, “oui”},
{“no”, “non”},

20
{NULL, NULL}
};
const char* FromEnglish(const DictEntry* dict, const
char* english);
const char* ToEnglish(const DictEntry* dict, const char*
foreign);
/* … */
int main() {
puts(FromEnglish(frenchDict, “yes”));
return 0;
}
Listing 20: A C ROMable dictionary
A Dict is an array of DictEntry. A DictEntry is a pair of const char* pointers, the
first to the English word, the second to the foreign word. The end of a Dict is marked by a
DictEntry containing a pair of NULL pointers. To complete the design, we add a pair of
functions that perform translation from and to English using a dictionary. This is a simple
design. The two dictionaries and the strings to which they point reside in ROM.
Let us now consider what happens if we produce a naïve object-oriented design in C++.
Looking at Listing 20 through object-oriented glasses, we identify a class Dict with two
member functions: const char* Dict::fromEnglish(const char* ,
and const char* Dict::toEnglish(const char*). We have a clean and
simple interface. Unfortunately, Listing 21 won’t compile. The static initializers
for frenchDict and germanDict try to access private members of the objects.
// NOT a ROMable dictionary in C++
#include
using namespace std;
class Dict {
public:
Dict();
const char* fromEnglish(const char* english) const;
const char* toEnglish(const char* foreign) const;
private:
enum { DictSize = 3 };
struct {
const char* english;
const char* foreign;
} table[DictSize];
};
// *** Following won’t compile ***
const static Dict germanDict = {
{
{“yes”, “ja” },
{“no”, “nein”},
{NULL, NULL}
}
};

21
// *** Following won’t compile ***
const static Dict frenchDict = {
{
{“yes”, “oui” },
{“no”, “non” },
{NULL, NULL}
}
};
// …
int main() {
cout << germanDict.fromEnglish("yes");
return 0;
}
Listing 21: A C++ ROMable dictionary NOT!
If we make these members public and eliminate the constructor as in Listing 22 , the class
will meet the criteria for static initializers and the code will compile, but we’ve broken
encapsulation. Users can see the internal implementation of the class and bypass the
intended access functions. Even worse, they can create their own (con-const )
instances of Dict whose internal state is outside our control.

// A ROMable dictionary in C++, but with poor


encapsulation
#include
using namespace std;
class Dict {
public:
const char* fromEnglish(const char* english) const;
const char* toEnglish(const char* foreign) const;
// PLEASE don’t access anything in the class below this
comment.
// PLEASE don’t create your own instances of this class.
enum { DictSize = 3 };
struct {
const char* english;
const char* foreign;
} table[DictSize];
};
onst static Dict germanDict = {
{
{“yes”, “ja”},
{“no”, “nein”},
{NULL, NULL}
}
};
const static Dict frenchDict = {
{
{“yes”, “oui”},
{“no”, “non”},

22
{NULL, NULL}
}
};
// …
int main() {
cout << germanDict.fromEnglish("yes");
return 0;
}
Listing 22 : A C++ ROMable corruptable dictionary
Now, let’s do it right. In Listing 23 , the class Dict in Listing 22 becomes Table, which is
nested privately within the new class Dict. Class Dict also contains, as a static
member, an array of Tables, which we can initialize statically. The function main() shows
use of this class Dict, which has a clean interface.
#include
using namespace std;
class Dict {
public:
typedef enum {
german,
french
} Language;
Dict(Language lang);
const char* fromEnglish(const char* english) const;
const char* toEnglish(const char* foreign) const;
private:
class Table {
public:
const char* fromEnglish(const char* english)
const;
const char* toEnglish(const char* foreign) const;
enum { DictSize = 3 };
struct {
const char* english;
const char* foreign;
} table[DictSize];
};
const static Table tables[];
Language myLanguage;
};
const Dict::Table Dict::tables[]= {
{
{
{“yes”, “ja”},
{“no”, “nein”},
{NULL, NULL}
}
},

23
{
{
{“yes”, “oui”},
{“no”, “non”},
{NULL, NULL}
}
}
};
// …
int main() {
Dict germanDict (Dict::german);
cout << germanDict.fromEnglish("yes");
return 0;
}
Listing 23: A clean C++ ROMable dictionary
So to make the best use of object-oriented design for data on ROM, special class design is
needed.Reality check
Having minutely examined the costs of C++features, it is only fair to see how C stands up
to the same degree ofscrutiny. Consider the C code in Listing 24 . One line contains
afloating point value, which will pull in parts of the floating pointlibrary and have a
disproportionate effect on code size if floatingpoint is not needed. The next line will have
a similar effect, if printf has been avoided elsewhere. The next line calls strlen(s)
strlen(s) times, rather than once, which has a serious impact on execution time.
/* Reality check – some things to avoid in C */
#include
#include
int main() {
char s[] = “Hello world”;
unsigned i;
int var =1.0;
printf(s);
for (i=0; i < strlen(s); i++) {
/* … */
}
return 0;
}
Listing 24: C reality check
Butwho would argue that these mistakes are reasons to not use C inembedded systems?
Similarly, it is wrong to brand C++ as unsuitable forembedded systems because it can be
misused.
Class libraries and embedded systems
Amajor benefit of using C++ is the availability of class libraries.Object-oriented design
makes class libraries easier to use (and harderto misuse) then their procedural
predecessors. But, as with procedurallibraries, some class libraries cause excessive code
bloat for thefunctionality they provide. If memory footprint is a concern, it is wiseto

24
evaluate a library before committing to it and measure what happensto memory footprint
when the library is used.
The Standard C++Library includes several components that are of use in a variety
ofembedded systems. STL containers and algorithms support handling ofcollections of
data. The std::string class supports strings of variablelength.
STL containers and std::string use the heap, withconsequences that have already been
discussed. Also, because STLcontainers and algorithms are templates, memory
consumption should betracked to make sure that they don’t lead to code bloat.
Thestd::ostream class supports outputting a string representation of anobject
independent of type, which can be useful for diagnostic purposesand in templates. Some
implementations, however – even some provided intoolchains for embedded systems –
have a very large code size for whatthey do. In a recent project, I had to write a lightweight
alternativeto std::ostream to avoid its excessive memory footprint.
Another valuable library for embedded systems is the boost library .The library is free. It
is widely used and is compatible with a rangeof compilers. It consists of well over 100
diverse classes and templatesincluding smart pointers, regular expressions, circular
buffers,command line parsing, logging, etc. Most components are independent ofthe
others, which keeps the memory footprint small.
But buildingboost for an embedded system is not a trivial undertaking, unless it
issupported by the toolchain provider. Instructions are provided on theboost web site,
including compilation with different compilers.
C++11 in embedded systems
Everythingsaid so far applies to C++11 (and C++14). Now let us examine the majornew
language features in C++11 from the point of view of runtime cost,both memory
consumption and execution time. We won’t discuss additionsto the Standard C++ Library,
which is outside the scope of this article.We will list features that have no runtime cost,
features that offerimprovements in either speed or size and finally, features that have
aruntime cost in either size or speed associated with their use. Thefollowing new features
in C++11 have no runtime cost:

auto The auto keyword has a new meaning in C++11 that is different from itsmeaning in
C. It means ‘automatic’ variable type determination. So ‘autox = y;’ means that the type of
x is the same as the type of y.

decltype Decltype evaluates the type of an expression.

override and final, =delete and =default The ‘override ’specifier on a


member function means that it overrides a memberfunction in a base class and it is an
error if no such base classfunction exists. Similarly, ‘final ’ means that this function
must not be overridden in a subclass. The =default and =delete modifiers are used
to control the use of compiler generated default functions like copy constructors.

Range-based for The range-based for statement ‘for (auto x: container ’ is

25
transformed by the compiler into a regular for loop. The for loops in the following
fragment in Listing 25 are equivalent.
#include
int main() {
std::vector v;
for (auto it = v.begin(); it != v.end(); ++it) {
auto& x = *it;
/* … */
}
for (auto& x: v) {
/* … */
}
return 0;
}
Listing 25: Range-based 'for '
Suffix return type C++11 allows the return type of a function to be at the end of
adeclaration instead of the beginning. This is useful in some templatecode. The following
statements in Listing 26 are equivalent.

int f();
auto f() -> int;

Listing 26: Suffix return type


Lambda Lambda expressions are equivalent to a locally-defined function objectclass and
have the same costs. The benefit of a lambda expression is amuch terser syntax.
initializer_list STL containers support initialization from an ‘initializer list’,
specifically template std::initializer_list. An initializer list can be stored in
ROM, so, although the STLcontainer occupies RAM, the data to populate it can have a
compactrepresentation in ROM.
Features that can increase speed or reduce size
Move constructors Thisfeature transfers the state of one object to another object
whileremoving it from the original object. Move constructors are provided bythe class
programmer and called by the compiler instead of a copyconstructor where it is known
that the original object state will nolonger be needed. A common example is when a
function returns an objectby value. Without a move constructor, this would result in the
objectbeing copied and then the original object being destroyed, bothpotentially
expensive operations.
The Standard C++ Libraryexploits move constructors, so using C++11 may yield
significantperformance improvements over C++03 in STL containers and
std::stringswithout any changes to application code.
constexpr The new keyword constexpr indicates an expression or a function that can
be evaluated at compiletime. It is fairly straightforward, for example to write a
constexprfunction that evaluates whether a number is prime.

26
But note theemphasis on the word ‘can’. A compiler that ignores the keyword is stilla legal
C++11 compiler and the things that can be done in a constexprfunction are limited. Loops
and variables are both prohibited. So use ofconstexpr can result in a function that
compiles, but is evaluated atruntime instead of compile time. This can be extremely
inefficientbecause of the limitations on constexpr functions.
We cananticipate compilers that make sophisticated computations at compiletime, saving
both memory and execution time, but we’re not there yet.Visit gcc.godbolt.org to
examine machine code generated by a number of modern compilers and how much they
do with constexpr functions.
noexcept Thenoexcept specifier guarantees that a function won’t throw an exception.This
allows the compiler to do some additional optimizations, which canspeed execution and
marginally reduce code size.
(Warning: It isleft entirely to the programmer to make sure that an exception
doesn’tpropagate to the point where it would leave a noexcept function. If
itdoes, std::terminate() is called. Typical compilers don’t warn about this even in
the most obvious cases.)
Speed or size penalties As explained above, using constexpr functions without checking
the codegeneration can result in inefficient runtime evaluation instead ofcompile time
evaluation.
More about lambdas
For mostC++11 features, once you understand what they do, it isn’t a big deal
tounderstand how they do it. But lambdas are not so easy.
Toexplore what lambdas do and how they do it, consider the followingcoding problem.
We need to write a function that counts the number ofvalues in the array that are less than
a parameter. In C, we might writesomething like this.
int count_less_than(int const* first, int const* last, int
value) {
int result = 0;
while (first != last)
if (*first++ < value)
++result;
return result;
}
Listing 27: C function to count integers less than value
When this was compiled using gcc 4.9.0 with option –Os on godbolt.org , the following
sequence of 10 machine code instructions was generated.
xorl %eax, %eax
.L2: cmpq %rsi, %rdi
je .L6
addq $4, %rdi
xorl %ecx, %ecx
cmpl %edx, -4(%rdi)
setl %cl
addl %ecx, %eax
jmp .L2

27
.L6: ret

Listing 28 : Machine code for Listing 28


In C++, we can use the count_if algorithm
(see http://en.cppreference.com/w/cpp/algorithm/count ) to eliminate the pointer
arithmetic. But count_if needs a function or functor to call that takes one argument.
So what’s a functor? A functor is an object that looks and acts like a function. It
has operator() defined that takes parameters and returns a result. Unlike a function,
afunctor may have internal state in the form of member variables.Functors are often used
in conjunction with algorithms like count_if to serve as callbacks.
A solution using count_if could look something like this:
#include
class less_than_threshold {
public:
less_than_threshold(int threshold): value(threshold) {
}
bool operator()(int x) const {
return x < value;
}
private:
int value;
};
int count_less_than(int const* first, int const* last, int value) {
less_than_threshold ltt(value);
return std::count_if(first, last, ltt);
}
Listing 29: Count integers less than a value using count_if and a functor
The Class less_than_threshold is a functor. It stores a threshold value in the
member variable value.When it’s called like a function it returns true if the parameter
tothe call is less than value. Now we’ve eliminated the pointer arithmeticfrom the C
example, but we had to add a whole class (less_than_threshold ) to do it.
Lambda expressions allow us to do all that much more succinctly. With a lambda
expression, we can add the class less_than_threshold at the point in the code
where we need it as shown in Listing 30.

#include
int count_less_than(int const* first, int const* last, int value) {
auto ltt = [value](int x) { return x < value; };
return std::count_if(first, last, ltt);
}
Listing 30: Count integers less than a value using a lambda
In this version, the ltt object is defined using a lambda expression. It captures the
localvariable value, takes a parameter x, and evaluates whether x is lessthan value.

28
The last example puts the lambda definition on a separate line for clarity. This isn’t
necessary. The function count_less_than can be reduced to one line as shown
in Listing 31 .
#include
int count_less_than(int const* first, int const* last, int value) {
return std::count_if(first, last, [value](int x) { return x < value; });
}
Listing 31: Count integers less than a value in one line
As shown in Listing 32 ,when the code from the previous three listings was compiled using
g++4.9.0 using –Os, machine code generated was identical in all three casesand the same
length as Listing 28.
xorl %eax, %eax
.L8: cmpq %rsi, %rdi
je .L11
xorl %ecx, %ecx
cmpl %edx, (%rdi)
setl %cl
addq $4, %rdi
addq %rcx, %rax
jmp .L8
.L11: ret
Listing 32: Machine code for Listing 29, Listing 30 and Listing 31
C++14 in embedded systems
C++14is a ‘maintenance upgrade’ of C++11 with minor extensions and bugfixes. For an
explanation of the major features and links to informationon compiler support, see The
C++14 standard and what you need to know.
Aswe did for C++11, we will examine the major features in C++14 from thepoint of view of
runtime cost, both memory consumption and executiontime. Compiler support for these
features is still patchy, but hopefullywill improve quickly.
Digit separators and binary literals C++14 adopts binary literals, indicated by a prefix of
0b or 0B, thatare already supported by some C and C++ compilers. It also allows asingle
quote (') to be used as a separator in both integer and floatingpoint literals. The separator
is ignored by the compiler, but makes thenumbers easier for humans to read. Here are
some examples:

Function return type deduction In C++14, we can declare a function return type as ‘auto’
and have thecompiler figure it out from return statements in the function. Thisseems to be
a step backwards in terms of separating interface andimplementation, but it is convenient
for small inline functions and ithas no runtime costs.
Variable templates Previously,templates were used only for generating classes and
functions. Now theycan be used to generate variables as well. For example:

29
template constexpr T pi = T(3.1415926535897932385);
// pi is pi as a float.
// pi is pi as a double
// pi<std::complex> is pi as a complex
number.</std::complex
Listing 33: Variable template
Features that can increase speed or reduce size
Generic lambdas As shown in Listing 34 ,generic lambdas are like templates, but with a
clearer, more elegant,syntax. Their size and speed trade-offs are similar to templates.
// Generic lambda with “auto” parameters
auto add = [](auto x, auto y) { return x + y; }
// How it might have been done, but wasn’t. Won’t compile.
template auto add = [](T1 x, T2 y) { return x + y; }
Listing 34: Generic lambda
Relaxed constexpr C++14is less restrictive about what it permits in a constexpr
function.Variables, if statements and loops are all allowed. This means that aconstexpr
function can be written more like a regular function and isless likely to produce poor
performance if evaluated at runtime. But thecaveat for C++11constexpr functions – that
they may be evaluated atruntime rather than compile time – still applies.
Generic lambdas and relaxed constexpr can improve size and/or speed, but if used
inappropriately can have the opposite effect.
Compilers and tools for embedded systems do this in 18 point and space after
MostC cross-compiler vendors for 32-bit and 64-bit targets offer C++compilers. But
because C, rather than C++, is still the default languagefor embedded development for a
resource-constrained environment, wecan’t assume that a successful toolchain has good
C++ support.
Here are some things to check when choosing a toolchain for embedded C++
development:

What size is ‘Hello World’? The memory footprint of ‘Hello world’ – the C++ version, not
the C version – may be surprisingly large.
How do strings and STL containers affect memory footprint? Add string and STL
container operations to ‘Hello world’ and see if theincrease in memory footprint is
reasonable. Although STL containers aretemplate instantiations, skilful library design can
minimize the amountof code unique to each instance. So, for example, anstd::map can
share a lot of code with anstd::map because they share the same keytype std::string and a
lot of the code in std::map depends only on thekey type, not the value type. Does the
std::map supplied with thecompiler exploit this?
Does the debugger work well for C++? Can you set a breakpoint in an inline function or
in a template? Doesit actually work if you do? Can you easily look at the state of anobject?
How well does it support C++11/14? You can get byperfectly well using C++03, but
support for C++11 and C++14 reflects thevendor’s commitment to supporting C++
developers. At the time ofwriting, compiler support for C++14 features is sparse.

30
Is boost available for/with the toolchain? Boost is a valuable library, but cross-
compiling it isn’t for thefaint-hearted. If the vendor has done it already, that’s a measure
ofthe vendor’s commitment and saves a lot of work.
Conclusion
MostC++ features have no impact on code size or on speed. Others have asmall impact
that is generally worth paying for. To use C++ effectivelyin embedded systems, you need
to be aware of what is going on at themachine code level, just as in C. Armed with that
knowledge, theembedded systems programmer can produce code that smaller, faster,
andsafer than is possible without C++.
Part 1: Modern C++ in Embedded Systems – Myth and Reality

Further information
The C++ Programming Language, 4th Edition by Bjarne Stroustrup is a very readable
reference book for C++11.
C and C++ Reference is good for immediate specific information about most versions of C
and C++ and their standard libraries.
Effective C++ in an Embedded Environment by Scott Meyers goes into much more detail
on some of the issuesaddressed here, including vtable implementation with
multipleinheritance, virtual inheritance, etc.
Godbolt's Interactive Compiler allows you to play with different C++ compilers and
instantly see the code generated.
Dominic Herity is a Principal Software Engineer at Faz Technology Ltd. He has 30years’
experience writing software for platforms from 8-bit to 64-bit,with full life cycle experience in
several commercially successfulproducts. He served as Technology Leader with Silicon &
SoftwareSystems and Task Group Chair in the Network Processing Forum. He hascontributed
to research into Distributed Operating Systems and HighAvailability at Trinity College
Dublin. He has publications on variousaspects of Embedded Systems design and has
presented at severalconferences in Europe and North America. He can be contacted at .
(Ref: Modern C++ in embedded systems – Part 1: Myth and Reality - Embedded.com)

31

You might also like