Using C++ in embedded systems – part 1

1. Introduction

It is possible to develop software for embedded systems using C++ as the main programming language. Usage of C++ has no real disadvantages regarding resource usage or performance compared to other programming languages like C. Instead using C++ introduces a lot of advantages even in the context of systems with limited resources. Still, C++ requires the knowledge over different features of the language and their consequences.

In this blog post, I want to give you a (not so) short overview over the impact of single C++-features as well as some general advice when the usage of these features should be considered. As in every complex topic, you can not solely rely on this but have to evaluate these advices on a case-to-case basis. Anyway you can take my advice as a starting point.

2. Embedded Software Engineering

The C++ programming language

When Bjarne Stroustroup began development of C++ based on C in 1979, he still called it “C with classes”. In 1983 he changed the name to C++. A first commercial implementation followed in 1985. C++ is still under further development today.

When development of C++ first started it mainly was an extension to C. It’s goal was to support the programming paradigm of “Object Oriented Programming” (OOP) 1 . With C still in it’s core (many parts of C++ have their roots in it’s predecessor C) , C++ tries to keep being backward compatible until today. That is why you could call C an subset of C++.

After various further extensions and continuous development, C++ has become what is known as a multi-paradigm programming language. It supports procedural programming (basically the C subset), OOP (C with classes) and even functional, generic or metaprogramming features in newer standards.

Development of C++ aimed to fulfill the following design principles:

  • As compatible to C as possible
  • Static typed
  • Support of procedural and object oriented programming
  • Platform independency
  • Zero-Overhead – no overhead for features not used

As you may have guessed, there is more than one C++ standard. Every released standard is called C++’yearOfRelease’. Those releases happen every three years. Every release includes new features that are complete or ready. The responsible committee decides which ones 2.

Embedded Systems

If we want to talk about C++ in embedded systems it’s crucial we know what we mean by embedded system. Actually there is no single official definition for the term embedded system. Mostly the definitions you’ll find will describe an embedded system the following way: “An embedded system is a computer system that is integrated (embedded) into a technical context.”.

Another property would be limited resources – especially code- and data-memory as well as CPUs with low processing power are regular limits. This is due to a focus on cost-reduction, since those systems are often produced in high numbers (think of electronic control units, your TV or your smartphone for example).

Personally, I consider hard- and software as a whole, when talking about an embedded system.

3. C++ in Embedded Systems

Contra

Many software engineers that specialize in embedded systems state that C++ is not suitable for systems with limited resources. Some statements you will find are the following:

  • It needs more code memory
  • C++ programs are slower than their counterparts written in C
  • They need more RAM than their counterparts written in C
  • C++ programs don’t behave deterministic
  • C++ creates large objects
  • The class libraries of C++ create large binary files
  • Virtual functions are slow
  • C++ creates bloated machine code
  • The abstraction of OOP causes inefficiency

Pro

On the other side there are people how recommend the usage of C++ in embedded systems (including me). They mostly call upon the following advantages C++ introduces:

  • Possibility to inline functions
  • No index checks during array accesses
  • Hardware access as efficient as in C
  • Strict type-checking done by the compiler
  • Less ambiguities because of namespaces
  • OOP / Data encapsulation allows for better software design

Exceptions

Many modern programming languages support exceptions and their handling (e.g. C++, C#, Modula-2, Java and PHP). They are meant to separate handling of failures from regular program flow 3. The idea behind is to make code more readable, more robust and more maintainable 4. While that may be true for many programming languages, the mechanisms for exception handling in C++ have so some shortcomings:

  • It may not be obvious that a function can throw an exception
  • Arbitrary variables can be used as exceptions (Java: Exception base-class)
  • Without a fitting catch-block exceptions are propagated to the top
  • There is no finally-block (as in Java for example)
  • There is no stack trace for C++ exceptions
  • Exceptions have to be used with great care in C++, since there is no garbage collection
  • There are invisible exit points introduced by exceptions, which results in breaking the code structure (similar to GOTO)
  • The compiler is not able to check whether all errors are handled during compile time
  • Performance of exception handling is strongly compiler dependent

Additional cost for exception handling:

  • One-time cost for the exception handling infrastructure (linked as soon as the first function is discovered, that may throw an exception)
  • Additional code for every try-catch block (but: Without exceptions there would be additional code for checking return values as well)
  • Runtime cost every time an exception is actually thrown
  • Additional code for every function that is allowed to throw exceptions (because of Stack unwinding)

Conclusion for exception handling:

C++ exceptions only partly stay true to their promise of higher code quality. In systems depending on codesize and runtime (both typical aspects of embedded systems) the cost for exception handling can be considered relatively high. According to Lippman the cost adds up to a memory overhead of 3 – 13% and a runtime overhead of 4 – 6%  5. This means whether using exception handling is a good idea or not should be pondered intensively. There are good reasons against it.

Type Casts

“Type-casting” means changing the type of a given statement into another type. There is a distinction between implicit casting and explicit casting. To keep being backward compatible to C, there are so called “C-style-casts”. The syntax to use this is (typename) expression but should not be used in C++ anymore. It can lead to undefined behavior quite fast. Additionally C++ type casts allow to find places in the code where casts are used much more easily. For you that means finding critical casts (those that could cause undefined behavior pretty fast) for further inspection causes less trouble.

dynamic_cast

Can only be used on pointers and references. Makes sure that the result of the cast is a valid and complete object of the requested class.

class Base {...};
class Derived : public Base {...};

Base base; 
Base* pointerBase;
Derived derived; 
Derived* pointerDerived;

pointerBase = dynamic_cast<Base*>(&derived); //Okay: Cast from derived class to base class
pointerDerived = dynamic_cast<Derived*>(&base); //Error: Cast from base class to derived cast

Dynamic_casts depend on Runtime Type Information (covered later on). The RTTI causes dynamic_cast to introduce runtime overhead, which is why you will not find it to be used in software for embedded systems very often. On top the need for dynamic_cast is often a hint for bad software design 6 . So, if you think you need dynamic_cast chances are high you should reconsider your design.

static_cast

Can be used for very cast that could happen implicitly, meaning those who have a converting rule. For example:

double pi = 3.14159265;
int integer = static_cast<int>(pi);

The example above shows an obvious case of lost accuracy.  Meaning those kinds of casts can cause data loss.

Another feature of static_cast is to cast related pointers into each other. That could be void- to unsigned char pointers or classes that are related by an inheritance hierarchy. The last one though will – contrary to dynamic_cast – not check the type, which can lead to runtime errors.

No runtime overhead introduced by static_casts, since the compiler evaluates it during compile time.

reinterpret_cast

This kind of cast forces the compiler to interpret a byte sequence as a variable of the given type. That means the casted statement will be interpreted by the compiler as if it had the targeted type. It’s obvious code becomes unportable pretty fast by this. Usage of this will probably more often than not result in undefined and/or unwanted behavior.

Still there is a use case which makes reinterpret_cast very useful especially in embedded systems. Accessing memory mapped registers via a struct:

#define ADDRESS_UART0 0x20001024
struct UartRegisters
{
  unsigned long regData;
  unsigned long regStatus;
  unsigned long regControl;
  // ...
};
void uartActivateRecvInt(unsigned long baseAddress)
{
  UartRegisters* regs = reinterpret_cast<UartRegisters*>(ADDRESS_UART0);
  regs->regControl |= 0x20;
}

Just as with static_casts, reinterpret_cast introduces no runtime overhead. The compiler evaluates it during compile time.

const_cast

Removes constness of an object or changes volatile. If you remove constness of an object it is syntactically possible to write to such an object. This will result in a runtime error though (code that tries to write to read-only memory). Also, const qualified objects tend to be const with good reason. Const_cast therefore is another dangerous operation for type casting.

A change of constness is nearly never necessary. Only real use case is calling a function of a non const correct library from within const correct code. Have an example:

void copyMemory (void *destination, void* source, unsigned int len);
int main()
{
    const char* str = “Hello World!”;
    char buf[100];
    copyMemory( buf, const_cast<char*>(str), 12);

    return 0;
}

Const_cast is another example of casts that are evaluated during compile time. So, no runtime overhead by using it.

Conclusion for type casting:

It is not always possible to completely do without casts. That is true for embedded systems as well as for any other application development. Anyway there are better suited and not so suitable cast operations as well as absolute exceptions of the rule (see reinterpret_cast).

Some general rules:

  • Don’t use C-style-casts but the C++-notation instead
  • Always do without dynamic_cast! It adds runtime overhead and is a sign of bad software design
  • Be cautious with static_cast – it often results in runtime errors
  • Const_cast should be exclusively used for calling non-const correct code in libs
  • If you have read anything new in this post – the special use case of reinterpret_cast is probably not for you, yet. Know what you are doing if you try it!

As we see, you can do without type casts close to completely. There are legitimate use cases but they are more the exception than the rule. Think hard when you use type casts and probably rethink your software design.

That was it for Part 1 ! Stay tuned and read about RTTI (Runtime Type Information), inline functions, inheritance and others in Part 2 of the “Using C++ in embedded systems” series!

Want to get better at coding in C++? I recommend you this books:

Disclaimer: Those are Amazon affiliate links. If you follow the links and buy the book I will earn a share of the price. You will not pay anything more than without my referal. Feel free to search for the books by yourself if you are not okay with this.

Over and Out!

Chrisys

Footnotes:

  1. Bjarne Stroustrup. The C++ Programming Language
  2. http://www.open-std.org/jtc1/sc22/wg21/docs/standards
  3. Bjarne Stroustrup. The C++ Programming Language
  4. C++ Performance: Common Wisdoms and Common “Wisdoms”. http://ithare.com/c-performance-common-wisdoms-and-common-wisdoms/
  5. Stanley B. Lippman. Inside the C++ Object Model.
  6. C++ Performance: Common Wisdoms and Common “Wisdoms”. http://ithare.com/c-performance-common-wisdoms-and-common-wisdoms/

1 thought on “Using C++ in embedded systems – part 1”

Leave a Reply

Your email address will not be published.