Embedded C Developers: To Hate Or Love C++? A Book Has The Answer

The story in short

 

Surveys (.i.e Eclipse IoT Developer Survey & IEEE Spectrum Survey ) and current state show clearly that embedded C still the dominated language for embedded system development. Also, most Silicon providers like (Nordic Semiconductor, Espressif, Silicon Labs, ..etc) provide SDKs implemented in C only, moreover, embedded C is the most supported and advised language to be used for embedded devices.

Maybe the most common SDK (If I can say so) that uses C++ besides the C is Arduino Core, where C++ is used intensively in the core and also in libraries. There are many reasons why developers generally prefer embedded C over C++, and probably the most obvious one, is that there is no hidden overhead behind C language features, especially that, in microcontrollers, each byte in RAM or ROM counts.

On the other hand, using C could introduce dangerous bugs; I’m especially talking about typecasting. While C++ overcomes these safety check weakness plus many other features. However, we can’t really talk about software design patterns in C  as there is no function overloading, templates, object-oriented, ..etc, and this is another advantage for using C++ instead of C.

This funny and sarcastic “I am a C programmer” video lists many advantages that C developers (embedded included) usually say/think about C++.

 

The famous CNLohr made this meme after finding a C bug in his C code for ESP8266

Personally, “I am a C programmer” who is not a fan of C language itself, but a fan of knowing the language I use in low-level details and where the resources are consumed. I know that there are many articles, tutorials and forum posts that have discussed that widely in a scattered way, but recently, I’ve got my hands on a book that made the difference for me and gave me a very good injection of information that made me think again of why not using c++ instead of embedded C. We usually say in Arabic world “People fear what they are ignorant of” and maybe this is the case for most embedded C programmers with C++.

This is not really a technical article rather than a review of a great book that has the answer to “Should we hate or love C++?” as embedded developers. So let’s get started!

 

 

The book name is “Real-Time C++: Efficient Object-Oriented and Template Microcontroller Programming” written by Christopher Kormanyos, and it has three editions.

 

About the book

 

The book introduces the reader, who doesn’t have any background in C++, to the language and its main features in the first chapter “Getting Started with Real-Time C++” using an example of a basic class to control LED toggle using a GPIO. The second Chapter “Working with a Real-Time C++ Program on a Board” teaches how to flash the code shown in the first chapter to a real MCU (ATmega from AVR family) using AVR-GCC toolchain and built using a Windows batch script build.bat.

The good thing of this practical book, that it is supplied with rich repo on Github, which is useful to explore even if you’re not a book owner. This chapter explains almost every line of the building process and each one of the flags used with building commands (.i.e selecting C++11 language standard with the -std=c++11 flag in avr-g++ command while compiling source code). Later in this chapter, some optimization is done in LED class implementation to reduce the App flash size from 36 bytes to 16 and RAM from 2 bytes to 0! The third chapter “An Easy Jump Start in Real-Time C++”  summarizes the most used features of C++ for the impatient C coders who want to adopt C++ quickly. The author described  that by saying “Developers new to real-time C++ may want to obtain some useful results quickly before taking the time to master all the intricate details of the C++ language .. a small subset of C++ can potentially be used for a wide variety of programming situations“.

Chapter four “Object-Oriented Techniques for Microcontrollers” discusses how to use the object-oriented (classes and inheritance relationship) in embedded code. The author proposed an extended LED class that has led_base class and led_port (specific for LEDs controlled with GPIOs) and led_pwm (specific for LEDs controlled with PWM signal). Many details specific to embedded efficiency while using object-oriented were declared in the book like Dynamic Polymorphism and its overhead as code size.

The next chapter is, chapter five “C++ Templates for Microcontrollers”, and I think it was not really “for microcontroller” as I found it more as a general discussion about using templates, but maybe the Metaprogramming concept was really a useful technique mentioned in this chapter which can do things during compiling-time instead of doing that in the embedded code. Chapter six “Optimized C++ Programming for Microcontrollers” talks about GCC available optimization options (-O2, -O3 and -Os) and other varied techniques like using assembly for time-critical tasks, storing variables in ROM when it’s available other than storing in the less available resource RAM and using metaprogramming. Optimization meant by the author in this chapter is not only the time-size optimization but also the code style and documentation are included.  Chapter six is the last chapter of the 1st part of the book “Language Technologies for Real-Time C++” of 3 parts in this book.

Part 2 “Components for Real-Time C++” starts with chapter seven “Accessing Microcontroller Registers” a light-weight chapter talking about accessing low-level registers in a C++ style using safety typecasting and templates. Later, the startup code is described in chapter 8 “The Right Start”, actually, it re-writes the startup code using C++ and can be found in the book’s repo. Right after, many examples of writing low-level drivers in C++ for GPIO, PWM, SPI and others are in chapter 9 “Low-Level Hardware Drivers in C++”. The last two chapters in part 2 talk about implementing features in c++ such as dynamic memory management in a way that is suitable for the target MCU in chapter 10 “Custom Memory Management” and implementing a “cooperative C++multitasking scheduler that performs a top-down call of its tasks using time slices and a basic priority mechanism” in chapter 11 “C++ Multitasking”.

The third part “Mathematics and Utilities for Real-Time C++” contains 6 different chapters on signal processing and mathematical utilities in C++ which are out of our focus area right now.

Finally, rich Appendices are the last part of this book containing short tutorials on different related topics.

The circuit used to test book codes in chapter 2

 

In general, the best thing about this book is being practical and the author tried to stick always to embedded examples as far as he could.

 

Highlights and useful notes from the book content

 

Const vs constexpr vs define with constants

 

const is the replacement of #define in C++ when dealing with constants. const will do what #define do plus type check, while #define only works as a textual replacement without any type-safety checks. C++11 introduced what is called constexpr which is a regular const plus ensuring that an integral value is a compile-time constant. This can be useful to make the compiler calculate a variable for you instead of making the firmware do that. This is also useful to make some constant variables in class only a compile-time constants, this will optimize the class size in RAM for sure. Here is an example from the book:

//Author: Christopher Kormanyos
class led_template
{
public:
led_template()
{
// Set the port pin value to low.
*reinterpret_cast<volatile bval_type*>(port)
&= static_cast<bval_type>(~bval);
// Set the port pin direction to output.
*reinterpret_cast<volatile bval_type*>(pdir)
|= bval;
}
static void toggle()
{
// Toggle the LED.
*reinterpret_cast<volatile bval_type*>(port)
^= bval;
}
private:
static constexpr port_type pdir = port - 1U;
};

Despite all class body, check the private section:

private:
static constexpr port_type pdir = port - 1U;

Where pdir has no meaning to be a real variable as it’s just needed once for declaring the led object and will not be changing later.

 

Static member of c++ class

 

Declaring a function in a class as a static member will reduce the code size as this potentially reduces the call overhead. More about static class member can be found in Tutorialspoint tutorial. Also declaring a shared class variable between all instances as static will save memory a lot. This is because this variable will have one copy not a several copies, one for each object. Using static members and constexpr is done in the folloµwing LED class:

//Author: Christopher Kormanyos
 #include <cstdint>
#include "mcal_reg.h"
class led
{
public:
// Use convenient class-specific typedefs.
typedef std::uint8_t port_type;
typedef std::uint8_t bval_type;
// The led class constructor.
led(const port_type p,
const bval_type b) : port(p),
bval(b)
{
// Set the port pin value to low.
*reinterpret_cast<volatile bval_type*>(port)
&= static_cast<bval_type>(~bval);
// Set the port pin direction to output.
// Note that the address of the port direction
// register is one less than the address
// of the port value register.
const port_type pdir = port - 1U;
*reinterpret_cast<volatile bval_type*>(pdir)
|= bval;
}

void toggle() const
{
// Toggle the LED via direct memory access.
*reinterpret_cast<volatile bval_type*>(port)
^= bval;
}
private:
// Private member variables of the class.
const port_type port;
const bval_type bval;
};

The enhanced version:

//Author: Christopher Kormanyos
template<typename port_type,
typename bval_type,
const port_type port,
const bval_type bval>
class led_template
{
public:
led_template()
{
// Set the port pin value to low.
*reinterpret_cast<volatile bval_type*>(port)
&= static_cast<bval_type>(~bval);
// Set the port pin direction to output.
*reinterpret_cast<volatile bval_type*>(pdir)
|= bval;
}
static void toggle()
{
// Toggle the LED.
*reinterpret_cast<volatile bval_type*>(port)
^= bval;
}
private:
static constexpr port_type pdir = port - 1U;
};

Results, before and after the changes:

 

Calling by reference or by value?

 

This could be a topic not specific to c++, but personally, I’ve found it a new speed optimization trick. The writer advises the reader to use function arguments by reference to decrease the time the MCU needs to push/pop from the stack

“ … C++ references are heavily used because this can be advantageous for small microcontrollers. Consider an 8–bit microcontroller. The work of copying subroutine parameters or the work of pushing them onto the stack for anything wider than 8 bits can be significant. This workload can potentially be reduced by using references.”

 

Overhead of Dynamic Polymorphism

 

A real embedded C programmer likes to see the cost of using any programming feature, and the book understands that in a way made the author try to explain the overhead of some of the C++ features. For example, it covered the concept of dynamic polymorphism and its overhead.

The book talked about virtual functions which can have its own definition in each child class. This was in the case of defining led_base  as parent class and led_pwm and led_port child classes. The toggle function was a virtual function as each child class will have its own definition; led_port will do the toggle using GPIO logic while the led_pwm will do it as a PWM signal.

The following code was used to show how the dynamic polymorphism will be used in real-time:

//Author: Christopher Kormanyos
void led_toggler(led_base& led)
{
// Use dynamic polymorphism to toggle
// a base class reference.
led.toggle();
}
void do_something()
{
led_toggler(led0); // Toggle an led_port.
led_toggler(led1); // Toggle an led_port.
led_toggler(led2); // Toggle an led_pwm.
led_toggler(led3); // Toggle an led_pwm.
}

Now, the overhead is described by :

“Many compilers store the addresses of virtual functions in a compiler-generated table at a location that could be either in static RAM or program code. A general rule-of-thumb, then, is that each virtual function of a class costs one chunk of memory large enough to hold a function pointer. Consider an 8-bit platform. If a function pointer requires two bytes on this platform and a derived class has three virtual functions, then the implementation of the derived class might require six bytes of storage for its virtual function table.”

But why this table is needed?

“Calling a virtual function can be efficient because all the compiler needs to do is to select the proper entry from the virtual function table and call it. This might be only slightly slower than a normal function call.”

 

A Function runs in compilation-time: Metaprogramming

 

This is a big concept and needs some research by the reader about it or to read from additional resources like C++ Template Meta Programming lecture or Wikipedia page, or geeksforgeeks tutorial. In short, it’s a way to make the compiler do a calculation that needn’t be done in run-time like the factorial function N!

N! ≡ N (N – 1) · · · 2 · 1 .

//Author: Christopher Kormanyos
template<const std::uint32_t N>
struct factorial
{
// Multiply N * (N - 1U) with template recursion.
static constexpr std::uint32_t value
= N * factorial<N - 1U>::value;
};
template<>
struct factorial<0U>
{
// Zero’th specialization terminates the recursion.
static constexpr std::uint32_t value = 1U;
};

Now, when we call this template

constexpr std::uint32_t fact5 = factorial<5U>::value;

The compiler will do the competition, not the embedded code, which will be a great way to optimize the code size for many things that aren’t needed to be in run-time.

 

Name Mangling and De-Mangling

 

This is another concept that is not related to C++ specifically, but worth mentioning. If you have ever  tried before to check the functions names as symbols with the compiler output using a tool like nm, which is a GNU tool to list the symbols from (.out file), then you must have seen the function name with additional characters. For example, get_event function could become something like that:

__ZN2os9get_eventENS_17enum_task_id_typeE

Why? This is because the compiler needs unique names for variables, functions, …etc and while we can have static variables or functions with same names in different source files, then the compiler need to distinguish between them. The same thing is applicable to c++ for overloading also.

To see the symbols in the way we are talking about, you can run the following line:

nm --numeric-sort yourappname.elf 

To de-mangle the name, C++filt is the tool used to produce the original name:

To read more check embeddedrelated article.

 

Use ROM, save RAM!

 

Most of the time we runout of RAM, as RAM has less size than ROM. One of the optimization tips in this book is to use ROM when it’s available. For example, storing software revision number in ROM instead of RAM by declaring the variable as const.

I remember that I used this trick once to save the RAM size consumed by debugging messages, while I had plenty of space in ROM.

You should check the symbol address in map file to make sure that the compiler considered replacing the const in ROM not RAM.

 

More reliability code

 

Using classes and other c++ features can add more reliability and safety levels to code. For example, The communication class object, the led class object or PWM class object are assigned to a specific peripheral, so if we copy this object to another object from the same class by direct assignment or using a constructor, then this causes the both objects to use the same embedded resources.

This can be prevented by defining a private constructor and overload the assignment operator in the class declaration like the following

//Author: Christopher Kormanyos
class led_base
{
public:
// ...
private:
// Private non-implemented copy constructor.
led_base(const led_base&) = delete;
// Private non-implemented copy assignment operator.
const led_base& operator=(const led_base&) = delete;
};

 

How to initialize a defined section in RAM

 

This is another good trick that is not related strictly to C++. This trick is about reaching a section defined in the linker script. This is sometimes quite important as this area could be not initialized in the startup code if the developer has selected the NOLOAD option.

Once, I needed in my job to erase a section used to store some variables to be available between software restarts (as RAM content can be retained after a software restart), and under a certain condition I had to erase this section, so instead of reaching each variable defined in that section separately , the developer can get a loop to initialize each location between start and end address of that defined section. A section can be defined like this

MEMORY
{
/*...*/
  NOINIT(rwx) :  ORIGIN = 0xXXXXXXXX, LENGTH = 0xXXXX
/*...*/
}

SECTIONS
{
/*...*/
  .noinit (NOLOAD):
  {
    PROVIDE(__start_noinit_data = .);
    KEEP(*(.noinit))
    PROVIDE(__stop_noinit_data = .);
  } >NOINIT
/*...*/
} INSERT AFTER .data;

And in the application source code can reach it by defining a start and an end addresses as extern variables provided from linker script (in the example above   __start_noinit_data and __stop_noinit_data).

extern std::uintptr_t __start_noinit_data;
extern std::uintptr_t __stop_noinit_data;
void init_noinit()
{
// Clear the noinit segment.
std::fill(&__start_noinit_data, &__stop_noinit_data, 0U);
}

 

What class constructors has to do with startup code?

 

Say you’ve declared a static object in code, then the compiler should call the constructor to make this object ready to be used before entering the main function.

The author says that “most c++ compiler … generates a subroutine with construction code for each one” of the objects and “the addresses of these compiler-generated subroutines are stored in a special linker section.”

The startup code should call these subroutines, and by the way, the author has written his own startup code with also the linker script.

// Author: Christopher Kormanyos
// Linker-defined begin and end of the ctors.
extern function_type* _ctors_begin[];
extern function_type* _ctors_end[];
void init_ctors()
{
std::for_each(_ctors_begin,
_ctors_end,
[](const function_type pf)
{
pf();
});
}

 

Finally

 

There are many different notes collected from the book worth mentioning but I decided to mention the most important ones. The reader can find his own notes while reading the book. Also, the author has attached a list of references at the end of each chapter and “additional reading” chapter for more information.

Our enthusiastic reader, Fahd Hussein, decided to buy the book after reading our review and took that creative shot!

Finally, If you have read unique book or article highlighting the C++ features with the corresponding overhead for embedded developers, then please mention it in a comment.

 

Exit mobile version