Im Bereich der Fahrzeugsoftware ist C++ wegen seiner Leistungsfähigkeit, Flexibilität und Low-Level-Steuerung eine beliebte Programmiersprache. Bei der Verarbeitung komplexer Datenstrukturen stehen Entwickler jedoch oft vor Herausforderungen, die sicherheitskritische Anwendungen erschweren können.
Ein Beispiel für die Vorzüge von C++ ist der Standardbibliotheksheader <tuple>. Dieser C++-Header bietet eine leistungsstarke und vielseitige Datenstruktur, mit der Entwickler verschiedene Datentypen zu einer einzigen Einheit gruppieren können, ohne eine eigene Klasse definieren zu müssen. In Fahrzeugsystemen kann dies von unschätzbarem Wert sein – sei es zur Darstellung von Sensorwerten, Fahrzeugzuständen oder Konfigurationsparametern für Steuerungssysteme.
Ebenso bietet der Header <chrono> aus der C++-Standardbibliothek Anwendungen verschiedene Typen zur Abstraktion von Uhren, Zeitpunkten und darauf basierenden Operationen. Er hilft Entwicklern, Zeit auf saubere, typsichere Weise zu verarbeiten, was für Systeme mit engen Zeitvorgaben unerlässlich ist. Allerdings verbergen sich hinter einigen seiner praktischen Funktionen auch Gefahren. Denn der C++-Standard enthält einige einfache, aber aussagekräftige mathematische Aussagen, die in der Praxis aufgrund der begrenzten und variablen Genauigkeit von Gleitkommazahlen nicht immer zutreffen.
Beide Header umfassen leistungsstarke Abstraktionen, insbesondere die Zeitmanipulation mittels <chrono> und flexible, typsichere Datenstrukturen über <tuple>. Diese Abstraktionen zielen darauf ab, Konvertierungen zu verbergen, die anfällig für komplexe Genauigkeitsfehler sind, und die komplizierten Regeln zur Überladungsauflösung zu umgehen.
In diesem Beitrag kommen Beispiele zur Sprache, die während der Entwicklung von SuperGuard, der funktionalen Sicherheitstestsuite von Solid Sands für C++, erarbebeitet wurden, um die potenziellen Fallstricke beider Header zu entmystifizieren. Darüber hinaus werden die Nuancen der Konstruktorauflösung und der Zeitdarstellung genauer analysiert, deren Bedeutung erklärt und gezeigt, wie automatisierte Tests dazu beitragen, C++ für kritische Systeme sicherer zu machen.
In sicherheitskritischen Bereichen wie der Fahrzeugindustrie, Luft-/Raumfahrttechnik oder der industriellen Steuerung muss Software strenge Anforderungen erfüllen. Normen wie ISO 26262 legen fest, wie Software entworfen, getestet und verifiziert werden muss. Diese Normen verlangen nicht nur funktionale Korrektheit, sondern auch Vorhersagbarkeit, Rückverfolgbarkeit und umfassende Tests über alle Softwareebenen hinweg, einschließlich der Sprach- und Bibliotheksebene.
Die C++-Standardbibliothek gilt im allgemeinen als sicher und zuverlässig, ist aber in der Praxis genauso anfällig für Sonderfälle und Implementierungsunterschiede wie Code auf Anwendungsebene. Insbesondere in den Bereichen generische Programmierung und numerische Berechnung können kleine Abweichungen vom erwarteten Verhalten große Auswirkungen haben.
Solid Sands hat SuperGuard entwickelt, um die C++-Standardbibliothek derselben Prüfung zu unterziehen, die normalerweise Anwendungslogik vorbehalten ist. Durch das Identifizieren von Mehrdeutigkeiten, das Überprüfen der Konformität und das Aufdecken versteckter Verhaltensweisen bietet SuperGuard die für den Einsatz in zertifizierten Systemen erforderliche Analysetiefe.
Das Tupel ist eine der leistungsfähigsten Datenstrukturen in C++ und ermöglicht es Entwicklern, verwandte Daten zu kombinieren, ohne eine vollständige Klasse deklarieren zu müssen. Ein gutes Beispiel ist ein Tupel, das die Position eines Fahrzeugs in einem 3D-Raum (x-, y-, z-Koordinaten) enthält, oder ein Tupel, das den Zustand verschiedener Sensoren in einem autonomen Fahrsystem darstellt. Tupel vermeiden unnötigen Performance-Overhead, eliminieren Boilerplate-Code für die Konvertierung und gewährleisten Typsicherheit – allesamt kritische Faktoren für Fahrzeuganwendungen, bei denen Effizienz und Zuverlässigkeit unerlässlich sind.
Obwohl Tupel erhebliche Vorteile bieten, sind sie nicht nativ Teil der C++-Sprache selbst. Stattdessen befinden sie sich in der C++-Standardbibliothek unter dem Header <tuple>. Dieser Header stützt sich stark auf die Template-Programmierung und führt eine Komplexitätsebene ein, die zwar leistungsstark ist, aber für Entwickler Herausforderungen mit sich bringen kann – vor allem beim Testen der funktionalen Sicherheit.
In der Fahrzeug-Softwareentwicklung werden Tupel in verschiedenen Systemen verwendet, von Sensorfusionsalgorithmen in autonomen Fahrzeugen bis hin zu Steuerungssoftware für das Motormanagement. Die korrekte Handhabung der Tupelkonstruktion und -konvertierung ist daher entscheidend für die Ergonomie und Effizienz des Codes und zur Vermeidung subtiler Probleme bei der Kompilierung, die sich indirekt auf die Zuverlässigkeit des Systems auswirken könnten.
C++ ermöglicht die Konstruktion von Tupeln auf verschiedene Weise. Dazu gehören:
Diese Flexibilität führt vor allem zu komplexerem Implementieren und Testen von <tuple>. Das Problem liegt in der Art und Weise, wie diese Konstruktoraufrufe aufgelöst werden, beispielsweise wenn ein benutzerdefinierter Typ A implizit aus einem Tupel von A konvertiert werden kann. Die Konstruktion eines Tupels aus einem anderen Tupel oder Typ könnte als Kopieren oder Verschieben interpretiert werden oder eine Konvertierung aus einem kompatiblen Typ auslösen.
Solche Szenarien erzeugen potenzielle Mehrdeutigkeiten bei der Konstruktorauflösung. In Fällen, in denen mehrere Konstruktoroptionen vorhanden sind, muss der Compiler entscheiden, welcher Konstruktor aufgerufen werden soll. Dieser Prozess, der als Überladungsauflösung bezeichnet wird, ist Teil des Typsystems von C++, kann jedoch schnell komplex werden, insbesondere wenn implizite Konvertierungen beteiligt sind. Wenn das Programm den Konstruktor nicht eindeutig auflösen kann, kommt es zu einem Kompilierungsfehler.
SuperGuard begegnet diesen Komplexitäten durch sorgfältig ausgearbeitete Tests, die sicherstellen, dass die Überladungsauflösung den beabsichtigten Konstruktor auswählt. Dies gewährleistet die korrekte Funktion der Tupelkonstruktion in allen Szenarien. In Fahrzeugsystemen, wo die Funktion vor der Bereitstellung gründlich validiert werden muss, ist der Einsatz solcher Tools unerlässlich. SuperGuard stellt sicher, dass Tests für C++-Tupelkonstruktoren nicht nur gründlich, sondern auch eindeutig sind. Es kümmert sich um alle Details der Konstruktorauflösung, sodass Entwickler sich keine Gedanken über unerwartete Verhaltensweisen oder Fehler machen müssen, wenn sie mit komplexen Tupeltypen arbeiten.
Was SuperGuard für Tests im Fahrzeugbereich besonders nützlich macht, ist seine Fähigkeit, verschiedene Konstruktorszenarien zu simulieren und so sicherzustellen, dass alle Grenzfälle abgedeckt sind. In dem Beispiel, in dem ein Tupel aus einem konvertierbaren Typ A erstellt wird, stellt SuperGuard sicher, dass der richtige Konstruktor ausgewählt wird, wobei explizite als auch implizite Konvertierungen berücksichtigt werden. Ohne diese sorgfältige Beachtung von Details könnten bei Tests kritische Probleme übersehen werden oder zu unklaren Ergebnissen führen.
Der Header <chrono> bietet Möglichkeiten zur impliziten Konvertierung zwischen eigener Zeitdarstellung und der von <chrono>-Uhren verwendeten implementierungsdefinierten Darstellung. So wird die Anweisung »0,0025 s warten« mit geringem bis keinem Aufwand in »2.500.000 ns« umgewandelt, um mit einer Uhr zu kommunizieren.
Diese Konvertierungen lassen sich am einfachsten direkt berechnen. Dies ist jedoch verlustbehaftet – eine Einschränkung, die im C++-Standard gut dokumentiert ist. Beim Vergleich unterschiedlich dargestellter Zeitpunkte versucht <chrono> durch einen zusätzlichen Schritt eine höhere Genauigkeit zu gewährleisten. Anstatt eine Seite in die andere zu konvertieren, werden beide in eine Zeiteinheit konvertiert, die einen gemeinsamen Bruchteil beider Werte darstellt.
Das bedeutet, dass ihre Werte nur multipliziert werden können. Die Multiplikation, die zum Erstellen dieser gemeinsamen Darstellung erforderlich ist, kann jedoch zu großen Zahlen führen, bei denen die Genauigkeit der Fließkommadarstellung verloren geht. Dies ist ein häufiges Problem, insbesondere wenn die meisten Implementierungen die Zeit in Nanosekunden messen.
Solid Sands hat festgestellt, dass der Standard und die meisten Implementierungen diese Eigenart der Funktion floor() bei der Verwendung von Fließkommazahlen übersehen. Die Funktion konvertiert einen Zeitpunkt von einer Darstellung in eine andere und rundet ihn ab. Das Ergebnis muss der späteste darstellbare Zeitpunkt sein, der nicht später als der ursprüngliche Zeitpunkt liegt. Dies wird mithilfe eines Operators sichergestellt, der Operanden in eine gemeinsame Darstellung umwandelt. Wenn die Implementierung den potenziellen Präzisionsverlust nicht berücksichtigt, kann der nächste darstellbare Zeitpunkt nach dem Abrunden dem ursprünglichen Zeitpunkt entsprechen – selbst wenn er theoretisch größer ist. Die Nachbedingung von floor() würde dann verletzt werden.
Dieses Problem und weitere werden durch den Einsatz eines Tools wie SuperGuard aufgedeckt. Sie verursachen subtile Unterschiede in den Ergebnissen und treten scheinbar willkürlich in bestimmten Anwendungsfällen und Konfigurationen auf. Da solche Diskrepanzen in den Funktionsspezifikationen nicht explizit dokumentiert sind, können sie leicht übersehen werden – ein ernstes Problem in sicherheitskritischen Systemen.
Die Komplexität der Konstruktorauflösung und Typkonvertierungen stellt insbesondere in sicherheitskritischen Systemen eine Herausforderung dar. Tatsächlich bietet <tuple> in erster Linie Leistungs- und ergonomische Vorteile, dennoch bleibt die korrekte Implementierung von zentraler Bedeutung. Mit SuperGuard wird jeder Fall gründlich geprüft, dokumentiert und getestet. SuperGuard kümmert sich um die feinen Details und gibt Entwicklern die Gewissheit, dass sie die Header <tuple> und <chrono> in einem sicherheitskritischen Projekt bedenkenlos verwenden können. Das Tool unterstützt die robuste Verifizierung kritischer Softwaresysteme und gewährleistet die korrekte Implementierung.
Max Blankestijn
ist Software Engineer bei Solid Sands.