Compiling and linking C++ code on Cortex-M3 without exception handling and RTTI

Most embedded software is written in C, and software written for ARM Cortex-M3 devices is no exception (pun intended).

C has many qualities – small, clean, relatively easy to learn and above all a superior interface for performing low-level hardware operations – try writing a routine for manipulating I/O memory  in Algol or Fortran …… Why, then, divert to C++ and its supposed steeper learning curve? The answer lies in the size of the software project you write.

The price for using C becomes painfully apparent when projects grow beyond the “hello world” stage – the boundary usually lies around 1200-1500 lines of code. Writing a “hello world” demo in C is feasible and maintaining the source code is not hard. “Hello world” applications are in the order of 10-30 KBytes of Flash on Cortex-M3.

However, many Cortex-M3 devices typically offer more than 256 KByte of Flash, up to 2048 KBytes of Flash. A typical “serious” project for such a device would employ a number of serial interfaces, would perhaps include an embdded web server, would do floating point calculations and would interact with a user through buttons or a touch screen. Such a project would require ten thousand lines of C code or more, which translates into upward of 200 Kbytes of flash programmable binary code. Maintaining order in this code, which is centered around C’s function-oriented set-up, is awkward and error-prone. Doing so with a large developer team is even harder, as I know from experience. In C, devices typically use structs which contain init and state information, but how do you know if and where all information is filled out correctly in 15000 lines of code? Also, manually calling init functions and combining common code into lower level function/data sets requires a lot of discipline from a development team. Most experienced C programmers confronted with situation will start to write code defensively, in an object-oriented approach, in order to prevent errors and bugs. In a way, they use C++features without using the proper tools.

C++ offers everything you need to maintain order in larger embedded programs. Object Oriented programming was a focal point when the language was invented, without sacrificing backwards compatibility with existing C code. Device information is maintained in the class container, togerther with all necessary functions. Common code can be brought into lower base classes, enabling the sharing of the code by virtue of the inheritance mechanism of C++. It is easy to define a base interface prototype in C++ which can be used in your application code, and then use polymorphism to link your interface prototype to your device implementation. Code migration becomes very easy – your application can be brought from, let’s say STM32 to LM3S, and the only thing that you must do is develop a new interface implementation.

Problems occur when C++ is used indiscriminately. The ambivalence of many C programmers towards C++ is based on the fact that a lot of things are included in your C++ binary (bloat) which you don’t need and use.  It is important, though, to understand two things about the superset of features that C++ has with regards to C:

  • There are compile-time enhancements in C++, such as classes, virtual functions or namespaces, which are completely benign and which in most cases will not contribute to bloat. They are part of the framework that enables programmers to scale their software concepts to larger dimensions without losing overview. In larger programs, C++ will most likely lead to more compact code – see this example at GCC compiler family, but this story is also applicable to KEIL and IAR users.

    Compile your code with the -fno-rtti -fno-exceptions flags (the others such as -ggdb aren’t relevant to this discussion but I included them anyway). My compiler lives on a FreeBSD system and is therefore placed in /usr/local/bin. I named it arm-eabi instead of the more common arm-none-eabi. This may be different on your system. When using Linux, for example, every distro seems to have its own peculiar spot to place applications.

    In order to prevent using exception handling and rtti at compile time:

    /usr/local/bin/arm-eabi-g++ -mthumb -c -pipe -mcpu=cortex-m3
    -mno-thumb-interwork -mapcs-frame -funsigned-char
    -fno-threadsafe-statics -Wall -Wimplicit -Wpointer-arith
    -Wswitch -Wredundant-decls -Wreturn-type -Wshadow
    -Wunused -Werror -Winline -ggdb -fno-inline
    -fno-stack-protector -fno-rtti -fno-exceptions
    -fno-unwind-tables  -Wa,-adhlns=LISTFILE -o OBJECTFILE CPPFILE

    LISTFILE, OBJECTFILE AND CPPFILE are of course to be properly named by you.

    This stops C++ from automatically inserting exception handling (and all other stdc++ library components) in your object file. But, this isn’t the complete solution! Even with the insertion of  -fno-exceptions at compile time, you will still get exception handling (and therefore bloat) when using C++ features such as new or delete. You will need to instruct the linker that it, too, should avoid all bloat components.

    At the linking stage, use the -nostdlib option to instruct the linker to omit any library file it would automatically add – in C++‘s case, libstc++.a . I use partial linking, and the linker command for that would be:

     

    /usr/local/bin/arm-eabi-g++
    -Wl,-Ur -nostdlib -Wl,Map=MAPFILE
    -o OBJCTFILE1 OBJECTFILE2 ...

     

    At the final linking step, or at the main linking step if you don’t use partial linking, you must compensate for the missing library components that you would want to include, for instance memcpy or assert. The standard C library provides many of these things and fills the missings references in most cases. Such a linking step would be like:

    /usr/local/bin/arm-eabi-g++ -mcpu=cortex-m3 -nostdlib
    -msoft-float -nostartfiles -fno-rtti -fno-exceptions
    -Wl,--cref -Wl,-Map=MAPFILE -Wl,-TLINKERSCRIPT -o ELFFILE
    -lc -lgcc -lm

    Functionality not found in libc.a such as new or delete must be provided by you – this is the price you pay for not using libstdc++! This, however, is not very difficult to do. In C++, you can use overloaded methods to insert your own version of new and delete:. I have an article that outlines how to write these functions.