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)

Zur Wahrung unserer Interessen speichern wir zusätzlich zu den o.g. Informationen die IP-Adresse. Dies dient ausschließlich dem Zweck, dass Sie als Urheber des Kommentars identifiziert werden können. Rechtliche Grundlage ist die Wahrung berechtigter Interessen gem. Art 6 Abs 1 lit. f) DSGVO.
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)