Mehr Sicherheit in der Programmierung Ada für Embedded

Dank Ada Drivers Library ist Embedded-Programmierung mit Ada auf dem ARM kein Problem (Bild: AdaCore).

Die Programmiersprache Ada eignet sich besonders für die Entwicklung von Embedded-Systemen. Ein reiches Typsystem, objektorientierte Programmierung, Multitasking und die vertragsbasierte Programmierung ermöglichen die Erstellung von sicherer und performanter Software.

Die Programmiersprache Ada verfügt über viele Features, die die Programmierung von eingebetteten Systemen erleichtern. Dazu gehört die Fähigkeit, sowohl eine hardwarenahe, effiziente Programmierung als auch problemnahe, elegante Lösungen zu ermöglichen – und zwar gleichzeitig. Ada wurde Ende der 1970er-Jahre entwickelt und erstmals 1983 standardisiert. Damit ist Ada jünger als C und etwa so alt wie C++. Die Sprache wurde von Anfang an mit dem Ziel des Einsatzes in eingebetteten und Echtzeitsystemen konzipiert. In diesem Bereich wird sie auch heute hauptsächlich eingesetzt, zum Beispiel in der Luft- und Raumfahrt, beim Militär, in Eisenbahnprojekten sowie im Finanzwesen.

Um die Lesbarkeit von Ada-Programmen zu erhöhen, geht Ada syntaktisch andere Wege als C und C++. So enthält Ada ein striktes Typsystem, das auch dynamisch zur Laufzeit überprüft wird; diese zusätzlichen Checks können auch abgeschaltet werden. Hinsichtlich der Effizienz steht Ada den Sprachen C und C++ in Nichts nach.

Ada bietet im Vergleich zu vielen anderen Programmiersprachen ein reiches Typsystem und erzwingt eine strikte Trennung zwischen Typen. So sind zum Beispiel in anderen Sprachen meist nur die vordefinierten Integer der Größe 8, 16, 32 und 64 Bit verfügbar, sowie 32- und 64-Bit-Floating-Point-Typen. In Ada hingegen können Integer- oder Floating-Point-Typen mit einem beliebigen Intervall – also zum Beispiel von 10 bis 20 – definiert werden. Echte Array-Typen, also nicht nur Syntax für Zeiger, gibt es ebenfalls, sowie außerdem Typen wie Strukturen oder Aufzählungen.

Was das Typsystem von Ada aber besonders nützlich macht, ist, dass es eine Reihe von statischen und dynamischen Überprüfungen nach sich zieht: So kann man in Ada nicht einfach eine Floating-Point-Zahl mit einem Integer addieren, sondern muss durch explizite Konversionen deutlich machen, ob die Addition denn nun eine Ganzzahl- oder Fließkomma-Operation ist. So eine Konversion ist auch nötig, wenn man zum Beispiel einen 64-Bit-Integer-Wert in einer 16-Bit-Variablen speichern möchte. Aufzählungen und Zeiger können nicht als numerische Werte verwendet werden, Zeiger-Arithmetik ist also nicht möglich und auch nicht nötig. Solche Einschränkungen erscheinen anfangs mühsam, wenn man aber merkt, dass dadurch einfache Programmierfehler vermieden werden können, gewöhnt man sich schnell daran.

Diese Überprüfungen können auch dazu verwendet werden, um Änderungen am Code – sogenanntes Refactoring – schnell und sicher durchzuführen. Wenn zum Beispiel die Größe einer Variable von 16 zu 32 Bit verändert wurde, reicht eine Kompilierung der Anwendung, um die Orte herauszufinden, die eine Anpassung erfordern.

Die erwähnten Überprüfungen werden vom Compiler statisch, also vor der Übersetzung des Programms in Maschinencode, ausgeführt. Andere Überprüfungen, zum Beispiel die Intervalle der vom Benutzer definierten numerischen Typen, werden dynamisch, das heißt während der Ausführung des Programms, überprüft. Diese Überprüfungen sind in der Regel sehr effizient, können aber für besonders Performance-kritischen Code auch abgeschaltet werden.

Erfahrene Embedded-Programmierer sehen das vielleicht mit etwas Skepsis, denn sie glauben zu wissen, dass bei der Low-Level-Programmierung ja doch wieder einfache Datentypen wie 32-Bit-Integer und Bitmanipulationen benötigt werden. Hier kommt ein Feature von Ada zum Zug: Die sogenannten Repräsentationsklauseln beziehungsweise »representation clauses«. Sie erlauben, die genaue Auslegung von so gut wie allen Ada-Datentypen im Speicher auf das Bit genau zu bestimmen, ohne auch nur einen einzigen Zeiger oder eine Bit-Operation zu verwenden. Dies erlaubt Source-Code von hohem Abstraktionsniveau. Zum Beispiel kann ein Ada-Programm so tun, als wäre ein Register eine Struktur mit mehreren Feldern, auf die man unabhängig zugreifen kann – während die Repräsentationsklausel erklärt, wie die einzelnen Felder auf die Bits des Registers abgebildet werden. Der Compiler erzeugt dann den Maschinencode mit Bit-Masken und Bit-Verschiebungen, den ein C-Programmierer selbst schreiben müsste.

In C gibt es auch sogenannte Bit-Felder mit einem ähnlichen Design-Ziel. Bit-Felder in C haben das Problem, dass sie wegen Endianness-Problemen nicht portabel sind, und die Größe von solchen Strukturen auch nicht portabel vorhergesagt werden kann. Deswegen sehen die meisten C-Programmierer von der Verwendung von Bitfeldern ab.