MODERN C++ IN EMBEDDED DEVELOPMENT: using c libraries

Language linkage

C++ programs can use C code. The linkage between C++ and software modules written in other programming languages is called language linkage. 

extern string-literal {declaration}

Standard guarantees only two language linkages: C++ and C. C++ is the default language linkage.

extern “C” {
	void my_c_function();
}

The above external linkage makes it possible to link C++ code with the C function my_c_function. That is, we can call my_c_function from our C++ code.

Name mangling

Name mangling is one of the reasons we need to specify language linkage when linking against C code. It’s the feature the C++ compiler uses to enable function overloading by generating function names with an encoding of the types of the function arguments. Let’s take a look at the following example:

void func(int a) {
}

void func(float a) {
}

int main() {
    func(1);
    func(1.0f);
    return 0;
}

Below is the disassembly of the compiled example:

_Z4funci:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        nop
        pop     rbp
        ret
_Z4funcf:
        push    rbp
        mov     rbp, rsp
        movss   DWORD PTR [rbp-4], xmm0
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        mov     edi, 1
        call    _Z4funci
        mov     eax, DWORD PTR .LC0[rip]
        movd    xmm0, eax
        call    _Z4funcf
        mov     eax, 0
        pop     rbp
        ret
.LC0:
        .long   1065353216

As we can see, the compiler generated two symbols: _Z4funci – our example function with int as the argument type, and _Z4funcf – our function with float as the argument type. 

To prevent name mangling and make linking with C code possible, we need to apply the extern “C” language linkage specifier to the C functions we want to link with C++ code. It allows us to call C functions from C++ code and vice versa. 

C standard library in C++

C++ wraps the C standard library and provides header files with the same name as the C language version but with a “c” prefix and no extension. For example, the C++ equivalent for the C language header file <stdlib.h> is <cstdlib>. 

In GCC implementation C++ wrappers include C standard library headers, for example <cstdio> includes <stdio.h>. If you dive into <stdio.h> you can see that it guards function declarations with __BEGIN_DECLS and __END_DECLS macros. Here’s the definition of these macros:

/* C++ needs to know that types and declarations are C, not C++.  */
#ifdef	__cplusplus
# define __BEGIN_DECLS	extern "C" {
# define __END_DECLS	}
#else
# define __BEGIN_DECLS
# define __END_DECLS
#endif

So, standard C library files take care of C++ compatibility, and this practice is also used in many HAL implementations provided by microcontroller vendors.

When including non-system C headers, in case they don’t guard function declarations with extern “C”, we can do that in a place where we include them:

extern “C” {
	#include “my_c_library.h”
}

Using C libraries in a C++ project 

In order to use a C function in C++ code, the function declaration needs to have a proper language linkage. Let’s look at a trivial example of a C++ class, MyClass, which has a method print_my_name.

 

#include <cstdio>

class MyClass {
    private:
        const char * my_name_ = "MyClass";
    public:
        void print_my_name () {
            puts(my_name_);
        }
};

int main() {

    MyClass my_obj;
    my_obj.print_my_name();

    return 0;
}

By including <cstdio>, we can use the puts function from C standard library. And there’s nothing wrong with this approach, but we can do much better in C++. 

We could wrap this functionality provided by a C library, in this case, a system library, and in the case of embedded systems, often, a vendor-provided function, in the Printer C++ class. 

The next would be to transform MyClass into a template class and instantiate it with different Printer classes. So what do we get by this:

  • Mocking C++ frameworks can usually mock only C++ classes, so now we can easily mock all the functionality provided in the C libraries we are using,
  • We separated the business logic from hardware-dependent functionality that vendors usually provide in C libraries and, as a result, have portable pieces of code that are configurable in compile time,
  • Not only can we mock functionality provided by C code, but we can also stub it and run simulations on target. 
#include <cstdio>

struct Printer {
    static void print_str(const char * str) {
        puts(str);
    }
};

template <typename P>
class MyClass {
    private:
        const char * my_name_ = "MyClass";
        P printer_;
    public:
        void print_my_name () {
            printer_.print_str(my_name_);
        }
};

int main() {

    MyClass<Printer> my_obj;
    my_obj.print_my_name();

    return 0;
}

The above example looks more complex than the first version of this program, but it’s just a basic template knowledge. And if you come from a C background, you must be sure the second version will result in a bigger flash footprint. Well, it turns out that the disassembly of both versions is the same:

.LC0:
        .string "MyClass"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret

This is often called a zero-cost abstraction. It might be a bit misleading terminology. Even though both versions will result in the same assembly code, the compiler will take more time to crunch the second one, so the more proper term would be a zero-run-time-cost abstraction. 

Amar Mahmutbegović

Amar Mahmutbegović

Head of Engineering

Embedded developer with a proven history in the development of BLE embedded firmware in C and C++. Co-founder of Semblie.

Semblie is a hardware and software development company based in Europe. We believe that great products emerge from ideas that solve real-world problems.