Ein Angebot von

C++ in Embedded Systemen: Lessons Learned!

| Autor / Redakteur: Matt Kline * / Sebastian Gerstl

Der Umstieg auf C++ kann für die Embedded-Entwicklung einige Hürden beinhalten.
Der Umstieg auf C++ kann für die Embedded-Entwicklung einige Hürden beinhalten. (Bild: gemeinfrei / CC0)

Zahlreiche Unternehmen steigen inzwischen für die Embedded-Firmware-Entwicklung von C auf C++ um. Mit C++ lässt sich Firmware entwickeln, die sicherer und expressiver ist. Doch einige Features können sich als zweischneidiges Schwert entpuppen.

Viele der C++ Features, z.B. Klassen, automatische Ressourcenbereinigung, parametrischer Polymorphismus und zusätzliche Typsicherheit, sind auf einem RTOS oder direkt auf der Hardware ebenso nützlich wie auf einem Desktop mit Universal-Betriebssystem.

Die „auto“-Magie von C++ ist jedoch ein zweischneidiges Schwert. Einige Sprachfeatures hängen von Systemfunktionen ab, die sich für Embedded-Umgebungen nicht eignen1. Die Toolkette anzupassen ist häufig auch komplex. libgcc und libstdc++ sollen nicht gänzlich verworfen werden, denn sie ermöglichen wichtige Funktionen wie memcpy, atomare Operationen und hardwarespezifische Floating-Point-Funktionalität; bestimmte Bereiche davon sind aber zu meiden.

Dieser Beitrag beleuchtet in Kürze, was wir bei der Umstellung unserer Firmware-Entwicklung auf C++ gelernt haben, und bietet eine hoffentlich hilfreiche Basis für ähnliche Unterfangen.

Erzeugen einer Toolchain

GCC eignet sich gut als Cross Compiler für sämtliche Targets, auch für die ARM-Systeme, die wir gewöhnlich in der Embedded-Entwicklung einsetzen 2. Einige Varianten lassen sich oft aus dem Package Manager der Linux-Distribution installieren, doch es ist ausdrücklich empfohlen, dass die Teams einen eigenen Cross-Compiler bauen und einsetzen. Das bietet etliche Vorteile:

  • Wenn das gesamte Team die gleiche Version der gleichen Toolkette verwendet, erhalten alle die gleichen Builds. Beim Debuggen und Testen ist das von Vorteil.
  • Compiler werden seit den letzten Updates auf C++ immer schneller entwickelt, und viele neuere Versionen ermöglichen eine erheblich verbesserte Codegenerierung 3. Bei einem früheren Projekt sind wir auf Compiler-Bugs in älteren Versionen (4.8.x) gestoßen, die zu einem Absturz unseres Systems führten.

Es ist meist aufwändig, eine ganze Cross-Compiler-Toolchain zu erzeugen. Gute Erfahrungen bestehen hier mit crosstool-NG. Damit können Sie Ihre Toolkette mittels einer Schnittstelle ähnlich Linux make nconfig konfigurieren.

Zudem unterstützt crosstool-NG Verwaltung und Download von Abhängigkeiten und führt die Builds für Sie aus. Die neueren Versionen ermöglichen auch die Bereitstellung beliebiger GCC-Quellen, z.B. die aktuellste Release. Die resultierenden Binärdateien lassen sich statisch verknüpfen. Die Verteilung ist also ebenso einfach wie ein tar-File der Toolkette zu erzeugen und es zentral bereitzustellen. Projektmitarbeiter, die die Toolkette verwenden, können dann über ein kurzes Skript auf sie zugreifen, sie extrahieren und ausführen.

Verknüpfen von C-Code

In unseren Embedded-Projekten gibt es oft zahlreiche C-Abhängigkeiten, z.B. herstellerspezifische Treiber und das RTOS. Erzeugen Sie diese mithilfe von gcc und wrappen Sie alle Header, die Sie über #include in C++ einbinden, mit extern “C“ { }. Ebenso sind sämtliche Funktionen, die Sie aus einer nicht-C++-Umgebung heraus aufrufen wollen, z.B. RTOS-Funktionen oder Startup-Assembly, mit extern “C“ zu kennzeichnen. Damit wird der Compiler daran gehindert, das sonst übliche „Mangling“ der Symbolnamen auszuführen.

Compiler-Flags

Exception Handling und RTTI sind ohne Speicherallokation schwierig bereitzustellen und lassen sich, falls gewünscht, über -fno-exceptions, -fno-non-call-exceptions und -fno-rtti deaktivieren. Beim Reboot wird die Firmware niemals auf die gleiche Weise beendet wie ein Userspace-Programm. Teardown-Code (einschließlich globaler Destruktoren) lässt sich mittels -fno-use-cxa-atexit verwerfen. Weitere hilfreiche Flags für die Embedded-Entwicklung sind z.B.

  • -ffreestanding: Gibt an, dass sich Ihr Programm in einer Umgebung befindet, in der es möglicherweise keine Standardbibliothek-Funktionen gibt und Ihr Programm nicht bei main() beginnt.
  • -fstack-protector-strong: Wird nachfolgend beschrieben.
  • -fno-common: Stellt sicher, dass jede globale Variable nur einmal deklariert wird, in einem einzelnen Objekt. Auf manchen Targets lässt sich damit die Performance verbessern.
  • -ffunction-sections und -fdata-sections: Teilen Funktionen und Daten eine eigene ELF Sektion zu. Damit kann der Linker mit der Option --gc-sections zusätzliche ungenutzte Funktionen eliminieren.

Diese Flags sind nicht C++ spezifisch, sollen aber an dieser Stelle erwähnt werden.

Sprachfeatures aktivieren

Wie erwähnt, benötigen einige hilfreiche C++ Features die Unterstützung des darunterliegenden Systems. In einer reinen Hardware- oder RTOS-Umgebung müssen wir diese bereitstellen.

Die meisten Details sind natürlich implementierungsspezifisch. Unser Beitrag basiert auf unserer Erfahrung mit ARM Cortex-M4 Boards mit GCC6 und ist hoffentlich ein guter Ausgangspunkt, auch wenn Projekte natürlich nicht in allem übereinstimmen.

Inhalt des Artikels:

Kommentar zu diesem Artikel abgeben

Schreiben Sie uns hier Ihre Meinung ...
(nicht registrierter User)

Kommentar abschicken
copyright

Dieser Beitrag ist urheberrechtlich geschützt. Sie wollen ihn für Ihre Zwecke verwenden? Infos finden Sie unter www.mycontentfactory.de (ID: 44953631 / Embedded Programmiersprachen)