en
de

MISRA-C:2012

Die Programmiersprache C ist verwirrend kompliziert, so dass auch erfahrene Ingenieure manchmal rätseln, was der Code denn macht – und warum. Ein Beispiel gefällig?

printf("%d", 4 + 1 % 5);

Nun gut, vielleicht hat man die Präzedenzregeln noch im Kopf und dann ist das „was“ und „warum“ schon geklärt. Das Verhalten von diesem Code wurde schon mal zum Compiler-Bug erklärt:

uint32_t a = 1;
int32_t b = -1;

if(a < b)
{
  printf("Ups");
}

Tatsächlich muss man aber nur bei Harbison/Steele oder im Standard unter „usual binary conversion“ nachschlagen, um das Verhalten – wenn auch überraschend – als völlig valide zu erkennen. Völlig abstrus wird es hier:

int x = 2;
int flag = 0;

switch(x)
{
  case 1:
    if(flag)
    {
  case 2:
      printf("Ups");
    }
    break;
}

Ja, es ist kompilierfähiger Code – wenn auch von der schlimmsten Sorte. Die nächste Steigerung besteht in unspezifizierten Verhalten – der C Standard schreibt in manchen Fällen nicht genau vor, was eigentlich passieren soll. Beispiel:

int i = 0;

printf("%d %d", i++, i);

Dieser Code kann mal „0 0“ und mal „0 1“ ausgeben – je nach Tagesverfassung des Compilers. Vor allem der Optimierer nimmt hier Einfluss, um die Entscheidung in die eine oder andere Richtung kippen zu lassen. Diese Spezifikationslücken sind absichtlich im Standard belassen worden, um Raum für möglichst effiziente Ausführung auf verschiedensten Plattformen zu lassen.

Noch mal schlimmer ist Code mit undefinierten Verhalten: solcher Code könnte wirklich alles bewirken. Wird zum Beispiel wild im Speicher geschrieben könnte dabei auch die Funktion zum Löschen des EEPROMs aufgerufen werden. Oder es passiert einfach gar nichts. Alles ist möglich, nichts garantiert. Nicht nur die Klassiker wie Speicherüberschreiber oder Division durch 0 gehören hierzu, sondern auch Bitshift-Operationen mit bestimmten Parametern.

Da mutet es fast harmlos an, dass manche Aspekte von C plattformabhängig sind, zum Beispiel das Fassungsvermögen der primitiven Datentypen.

MISRA-C:2012 zur Hilfe

Um Entwickler vor diesen Untiefen zu warnen wurde der MISRA-C Standard entwickelt. Er bündelt in Summe die Erfahrung vieler hundert Jahre Programmiererfahrung mit C. Die aktuelle Version MISRA-C:2012 besteht aus 16 Direktiven und 143 Regeln auf insgesamt 236 Seiten. Noch ist allerdings größtenteils der Vorgänger MISRA-C:2004 in Gebrauch, teilweise sogar die noch ältere Version MISRA-C:1998.

Die Einhaltung der Direktiven sollen manuell von Menschen in Code Reviews geprüft werden während die Regeln automatisiert von einem Prüfwerkzeug überwacht werden können. Beispiele für Direktiven in verkürzter Form:

  • Die Plattform-Spezifika sollen verstanden und dokumentiert sein
  • Anforderungen sollen auf Code rückverfolgbar sein und umgekehrt (Traceability)
  • Defensive Programmierung
  • Fehlercodes sollen abgetestet werden
  • Die Entwickler sollen eher Funktionen als Makros verwenden
  • Gut lesbare Bezeichner
  • Keine dynamische Speicherverwaltung

Es ist offensichtlich, dass diese Regeln nicht automatisiert geprüft werden können. Wie sollte ein Automatismus die Bedeutung eines Rückgabewertes als Fehlercode erkennen? Wie sollte das Vorhandensein gewisser Dokumente am Code abgelesen werden? Wie sollte die ausreichende Bevorzugung von Funktionen gegenüber Makros automatisiert festgestellt werden?

Diese Einteilung in Direktiven und Regeln ist neu bei MISRA-C:2012. Vormals waren die Direktiven auch Regeln und es gab auch Versuche einer automatisierten Prüfung. Diese Versuche erzeugten aber vor allem viele Fehlwarnungen und die Illusion, dass geprüft worden ist, was nicht automatisiert prüfbar ist. Diese Trennung stellt also eine wesentliche Verbesserung in der automatisierten Prüfbarkeit der verbliebenen Regeln dar.

Auch neu ist die wesentlich präzisere Formulierung der Regeln und die klare Trennung zwischen Anforderung und Erklärung. STOPPED

Auflösung und einige Regeln

Was sagt nun MISRA-C zu obigen Code-Beispielen? Regel 12.1 schreibt vor, dass die Auswertungsreihenfolgt mit Klammern verdeutlicht werden muss:

printf("%d", 4 + (1 % 5));

Der Code gibt also 5 aus. Die Regeln 10.1 ff. verbieten implizite Konvertierung zwischen signed und unsigned. Explizit hingeschrieben passiert folgendes:

uint32_t a = 1;
int32_t b = -1;

if((uint32)a < (uint32)b)
{
    printf("Ups");
}

Regel 13.2 verdammt unspezifizierte Ergebnisse, man müsste also schreiben:

printf("%d %d", i, i);
i++;

Im Kopf behalten muss man die 143 Regeln zum Glück nicht – dafür gibt es ja die automatische Prüfung. Und für Code Review macht man sich am besten eine Checkliste mit den relevanten Direktiven.

Verbesserungen gegenüber MISRA-C:2008

Die Überarbeitung von MISRA-C hatte zwei Ziele: die Unterstützung von C99 einzuführen und die Regeln zu präzisieren und verständlicher zu gestalten. MISRA-C:2008 hatte nur C89 zum Gegenstand. Die Hersteller der Prüfwerkzeuge folgten jedoch dem Druck des Marktes und implementierten die Prüfung von C99 Code gegen MISRA-C. Das Ergebnis dieser Prüfung basierte jedoch auf der Spekulation des Herstellers, wie MISRA-C für C99 aussehen könnten. MISRA-C:2012 gilt für C89 und C99.

Der Präzisierung dient die Einteilung in Direktiven und Regeln, die klare Trennung von Anforderung und weiterer Erläuterungen sowie einige weitere hilfreiche Angaben. Die Präzisierung der Regeln sollte zu einer Harmonisierung der derzeit durchaus unterschiedlichen Verhaltens verschiedener Prüfwerkzeuge führen.

Direktiven sind Richtlinien, die nicht automatisch sondern in Code Reviews manuell geprüft werden. Einige Regeln wurden in Direktiven umgewandelt, weil sich eine sinnvolle automatische Prüfung als nicht praktikabel erwiesen hat. Gerade bei der Interpretation der nun in Direktiven umgewandelten Regeln haben sich die Prüfwerkzeuge deutlich unterscheiden.

Jede Regel besteht wie bisher aus der Anforderung in einem kurzen Satz. Die Erklärung der Anforderung ist nun aufgeteilt in die Präzisierung der Anforderung (amplification) und die Begründung und Erläuterung der Anforderung (rationale) sowie zahlreiche Beispiele. In der Vergangenheit wurden teilweise aus der Begründung der Anforderung weitere Anforderungen herausgelesen die nicht der Intention der Autoren entsprachen. Durch die Trennung wurde eine weitere Quelle von Unterschieden zwischen den Prüfwerkzeugen beseitigt. Die Präzisierungen sind nun wesentlich umfangreicher und exakter. Unter der Exaktheit hat teilweise die Verständlichkeit gelitten. Dies wird hoffentlich durch die ausführlicheren Erklärungen  abgemildert. Die Exaktheit und war jedoch unbedingt notwendig, um das Verhalten der Prüfwerkzeuge zu harmonisieren.

Trotz der zahlreichen Änderungen hat sich bezüglich des Umfangs und der ursprünglichen Intention der Regeln kaum etwas geändert. Eigentlich sollten sich also beim Umstieg auf MISRA-C:2012 kaum Unterschiede bezüglich der von Prüfwerkzeugen generierten Warnungen ergeben. Durch die Präzisierung wird aber klar, dass gewisse Interpretationen der Regeln nicht korrekt oder zumindest von MISRA nicht beabsichtigt waren. Will ein Hersteller also MISRA-C:2012 korrekt umsetzen, muss er eventuell einige Anpassungen an den generierten Warnungen vornehmen. Das kann schon einige Hundert oder Tausend zusätzliche Warnungen bei einer mittelgroßen Code-Basis ergeben.

Verfügbarkeit von Prüfwerkzeugen

Für MISRA-C gibt es zahlreiche Prüfwerkzeuge. Einige davon unterstützen bereits die neue Version MISRA-C:2012:

  • Parasoft
  • LDRArules von LDRA
  • QA-C von Programming Research
  • PC-lint von Gimpel

Klocwork verspricht eine baldige Umsetzung. Diese Liste erhebt jedoch keinen Anspruch auf Vollständigkeit!

Wann umsteigen?

Die erwartete Harmonisierung der Prüfwerkzeuge durch MISRA-C:2012 wird Zeit beanspruchen. Erste Implementationen von MISRA-C:2012 werden dazu tendieren, das bisherige Verhalten des Prüfwerkzeugs möglichst wenig zu ändern, oder aus anderen Gründen MISRA-C:2012 fehlerhaft umsetzen.

Wechselt man also zum jetzigen Zeitpunkt auf MISRA-C:2012 und ein anderes Prüfwerkzeug, erzielt man nicht die beabsichtigte Unabhängigkeit von einem speziellen Prüfwerkzeug, sondern erkauft sich mit vielerlei Migrationsproblemen die Abhängigkeit von einem anderen Prüfwerkzeug.

Ein Umstieg von MISRA-C:2004 auf MISRA-C:2012, bei dem das Prüfwerkzeugs beibehalten bzw. nur aktualisiert wird, sollte derzeit probehalber geschehen. Dabei könnte entdeckt werden, dass die Implementation von MISRA-C:2012 noch zu viele Fehler enthält. Die Bewertung sollte durch einen Experten erfolgen.

 

Kommentare (0)

×

Updates

Schreiben Sie sich jetzt ein für unsere zwei-wöchentlichen Updates per E-Mail.

This field is required
This field is required
This field is required

Mich interessiert

Select at least one category
You were signed up successfully.