2.3 Kommentare dürfen nicht verschachtelt werden.
Das ist korrekt; Kein C-Standard definiert(e) verschachtelte Kommentare.
2.2 Es darf nur /* dieser */ Kommentar verwendet werden.
Das ist korrekt; Der bezogene C-Standard definiert gar keinen anderen Kommentar. Jedoch ein neuerer C-Standard definiert immerhin seit 1999 den // Zeilenkommentar, der die Programmierer dringend dazu animieren soll, mehr Kommentare zu schreiben, was auch eingetreten ist, dort, wo // verwendet werden darf.
2.4 Code-Teile sollten nicht auskommentiert werden.
Es ist wichtig und erhöht das Verständnis, Code-Teile auskommentieren zu können! Daran ist konkret erkennbar, was einmal zusätzlich oder alternativ implementiert war und (an jener Stelle) eventuell wieder aktiv gesetzt werden kann.
5.5 Kein Objekt- oder Funktions-Identifizierer mit statischer Speicherdauer sollte wiederbenutzt werden.
Unklar in mehreren Hinsichten. Regelprüfprogramme verwenden diese Regel nicht.
5.6 Kein Identifizierer in einem Namensraum sollte gleichlautend wie ein Identifizierer in einem anderen Namensraum sein, mit Ausnahme von Struktur- und Union-Mitgliedern.
Unklar in mehreren Hinsichten. Genaue Kenntnis des C-Standard erforderlich. Regelprüfprogramme verwenden diese Regel nicht.
5.7 Kein Identifizierer-Name sollte wiederbenutzt werden.
Inwiefern nicht? Vollkommen unklar! Regelprüfprogramme verwenden diese Regel nicht.
6.3 Anstelle der Basistypen (char ... long double) sollten typedef-Namen verwendet werden, die Größe und Vorzeichenbehaftung anzeigen.
Zusätzlich zu den Basistypen wäre besser. Beispielsweise BOOL CHAR BYTE INT2 INT4 UNS2 UNS4 MESS TIME. Es ist Unfug, die Basistypen zu verbannen. Es gibt viele vollkommen sichere (lokale) Verwendungen für die Basistypen, die dann dadurch zudem vollkommen portabel sind.
8.4 Falls Objekte oder Funktionen mehr als einmal deklariert werden, müssen ihre Typen kompatibel sein.
Wo jeweils? Unklar. Zudem meist genaue Kenntnis des C-Standard erforderlich. Grundsätzlich sind beliebig viele gleiche Deklarationen ein und desselben Objekts möglich.
8.8 Ein externes Objekt oder Funktion darf nur in einer Datei deklariert werden.
Unklar. Müssen Deklarationen in genau eine Datei geschrieben werden, die dann in alle .c-Dateien eines Projektes inkludiert wird? Regelprüfprogramme verwenden diese Regel nicht.
8.10 Alle Deklarationen und Definitionen von Objekten und Funktionen auf Dateiebene müssen interne Bindung haben, sofern externe Bindung nicht erforderlich ist.
Okay. Siehe 8.11
8.11 Der Speicherklassenspezifizierer static muss verwendet werden in Definitionen und Deklarationen, die interne Bindung haben.
Verwirrend. Die interne Bindung entsteht erst durch static. Diese Regel sollte besser mit Regel 8.10 verschmolzen werden.
8.12 Wenn ein Array mit externer Bindung deklariert ist, muß seine Größe explizit angegeben oder durch Initialisierung implizit bekannt sein.
Verwirrend! Bei einer extern-Deklaration ist eine Initialisierung nicht möglich. Nur bei Definition möglich.
10.6 Ein Suffix U muß angewendet werden bei allen Konstanten des Typs unsigned.
Verwirrend und unvollständig. Der vorzeichenbehaftete Typ wird erst durch den Suffix vorzeichenlos. Im Regelfall können über 99% aller Integerkonstanten ganz sicher als int belassen werden, auch bei Verknüpfung mit vorzeichenlosen Operanden. int-Werte bis 32767 sind portabel sicher. Auch hier aufpassen: ~0u, ULong= 1u<<15. 1<<15 ergäbe hier 0xFFFF8000ul, auf einer Plattform mit 16 Bit int. Diese Regel sollte mit Regel 10.5 verschmolzen und erweitert werden: Überall (nicht nur bei Konstanten) wo es zu einer Erweiterung (durch int-Promotion oder Verknüpfung mit breiteren Typen) mit unerwünschtem Erhalt des Vorzeichens kommen kann, muß die Vorzeichenbehaftung entfernt werden.
11.1 Konversionen dürfen nicht stattfinden zwischen einem Funktionszeiger und irgendeinem anderen Typ als einem integralen Typ.
Verwirrend. Ein Funktionszeiger darf also beispielsweise einem Ganzzahl-Objekt vom Typ char zugewiesen werden. Das ist ganz erstaunlich.
11.2 Konversionen dürfen nicht stattfinden zwischen einem Objektzeiger und irgendeinem anderen Typ als einem integralen Typ, einem anderen Objektzeigertyp, oder einem void-Zeiger.
Verwirrend und stark einschränkend. Hinsichtlich des integralen Typs gilt das gleiche wie bei Regel 11.1. Es dürfen keine Lib-Funktionen verwendet werden, deren Name mit mem beginnt und auch keine sonstigen Funktionen, die einen Parameter des Typs void* besitzen. Der C-Standard verlangt, daß bei Konversionen zu und von void* keine Wertänderung passieren darf.
12.10 Der Komma-Operator darf nicht verwendet werden.
Ein Totalverbot ist übertrieben und unbegründet. In der Kopfzeile einer for-Schleife und in if-Zweigen mit wenig Text und ohne { } sollte der Komma-Operator mindestens erlaubt sein. Zudem kann er den (angeblichen!) booleschen Kontext if (b=a,++a, a>4) auf elegante Art beseitigen. Siehe auch 13.6 unten.
12.12 Die Bit-Repräsentation von Gleitkommawerten darf nicht verwendet werden.
Gegen diese Regel muß aber zwingend verstoßen werden, da wegen 16.1 und 20.9 alle Konversionsfunktionen verboten sind. Es müssen folglich eigene Konversionsfunktionen entwickelt werden, die Gleitkommawerte in lesbare Zeichenketten unter Verwendung verschiedener wählbarer Formate umwandeln. Dazu müssen die plattformspezifischen Bitbedeutungen aller Gleitkommatypen bekannt sein und verwendet werden.
13.1 Zuweisungsoperatoren dürfen nicht in Ausdrücken verwendet werden, die einen booleschen Wert ergeben.
Unklar. Falls if (a = b) gemeint ist, so wäre das falsch. Der C-Standard definiert gar keinen booleschen Kontext! Es liegt hier vielmehr ein skalarer Kontext vor, der irgendeine Ganzzahl oder Gleitkommazahl mit beliebigem Wert erwartet. Einen booleschen Wert vom Typ int erzeugen die Vergleichs- und die logischen Operatoren.
13.4 Der kontrollierende Ausdruck einer for-Schleife darf nicht irgendein Gleitkomma-Objekt enthalten.
Hin und wieder ist es notwendig, genau dies doch zu tun. Es kann nicht immer mittels eines mitlaufenden Ganzzahl-Objekts vermieden werden. Und was ist daran problematisch, bezüglich Sicherheit, einen Gleitkommawert bei jedem Durchlauf zu prüfen? Das kann sicherer sein als Vermeidungskonstrukte zu implementieren.
13.6 Iterativ verwendete Variablen in einer for-Schleife dürfen nicht im Schleifenkörper verändert werden.
Entweder der Komma-Operator ist erlaubt oder es müssen öfter zwangläufig mehrere iterativ verwendete Variablen im Körper verändert werden! Das Verbot von Zeigerarithmetik fördert dies zusätzlich. Regelprüfprogramme verwenden diese Regel nicht. Beispiel: Eine Variable wird bei jedem Durchlauf um 1 erhöht, eine weitere wird um 4 erhöht, eine weitere wird um 1 reduziert, ein Struktur-Zeiger wird inkrementiert. Alle diese Variablen sollten in der Kopfzeile der Schleife links initialisiert und rechts verändert werden, was wegen des Verbots des Komma-Operators jedoch nicht möglich ist. Nebenbei bemerkt ist eine solche Verwendung eines Struktur-Zeigers sehr performant.
14.3 Vor der Arbeit des Präprozessors, eine Leeranweisung darf nur alleine in einer Zeile stehen; ein Kommentar darf folgen, wenn nach der Leeranweisung zunächst ein Whitespace-Zeichen folgt.
Etwas verwirrend. Jedenfalls ist { ; } optisch bedeutend auffälliger als ein einzelnes ; allein in einer Zeile. Mehrere Leeranweisungen ;;;;; wären ebenfalls auffälliger als ein einzelnes.
14.4 Die goto-Anweisung darf nicht verwendet werden.
In eher seltenen Fällen kann goto außerordentlich hilfreich sein! Vermeidungs-Code ist in diesen Fällen meistens problematisch, unübersichtlich und unsicher. Und ein anderes Konzept deshalb wäre sehr viel aufwendiger, anspruchsvoller und zeitraubend. Beim EmbeddedProgramming ist goto nahezu unverzichtbar, da sehr ressourcenschonend. Es ist nicht sinnvoll, daß ein Microcontroller mit beispielsweise 2 MIPS und 4096 Byte RAM an seine Grenzen stößt und seine Aufgabe nicht mehr (ganz richtig) erfüllen kann, nur weil im Code auf goto (und andere Anweisungen) verzichtet werden mußte. Es ist sehr gefährlich, einen Microcontroller in die Nähe (einer) seiner Ressourcengrenzen zu fahren. Es kann (sporadisch) undefiniertes Verhalten entstehen.
14.5 Die continue-Anweisung darf nicht verwendet werden.
Dieses Verbot ist nicht nachvollziehbar. Es ist sehr prägnant, übersichtlich und sicher, als erste Zeile des Schleifenkörpers beispielsweise if (sfco->a==0 || sfco->id==0) continue; zu codieren. Der Vorteil ist, daß an einer einzigen Zeile die gesamte Aktion und deren Grund ersichtlich ist. Vermeidungs-Code hat selten diesen Vorteil und wirkt hier unnatürlich und verkrampft.
14.6 Jede Iterations-Anweisung betreffend, es darf höchstens eine break-Anweisung vorhanden sein, um eine Schleife zu beenden.
Falls zwei oder mehr break; gebraucht werden, wird die Situation problematisch. Vermeidungs-Code kann sehr häßlich und der Übersichtlichkeit abträglich sein.
14.7 Eine Funktion darf nur einen einzigen Punkt des Verlassens an ihrem Ende besitzen.
Dies ist ein schädliches Verbot, besonders im Zusammenhang damit, daß goto verboten ist. Siehe auch 14.5. Sehr oft muß programmiert werden, daß eine Funktion in ihrem vorderen Teil verlassen werden kann mit verschiedenen Rückgabewerten, aufgrund einer Prüfung ihrer Parameterwerte mit negativem Ergebnis. Oft ist es sinnvoll, eine Funktion nach default: in einem switch zu verlassen. Es gibt auch Funktionen, die konzeptionell hunderte von return value; enthalten. Compiler generieren (auf Assemblerebene) in solchen Fällen oft automatisch Sprünge zu einem Exitpunkt. Es sind auch Funktionen bekannt, die mehrere Abteilungen haben, mit jeweils einem eigenen Exit. Wenn nur ein return am Ende erlaubt ist, wird Vermeidungs-Code in der Regel eine sehr einengende problematische Angelegenheit werden. Es ist übel, wenn man aufgrund fehlender Sprachmittel einer Programmiersprache (MISRA-C) überall zu unnatürlichen Ersatzkonzepten gezwungen wird. Die natürliche Reaktion darauf besteht darin, daß man sich eine geeignetere Programmiersprache aussucht. Andere Programmiersprachen erlauben ebenfalls die beliebige Verwendung einer return-Anweisung. Von dort ist allerdings nicht bekannt, daß wichtige Sprachmittel durch Verwendungsverbote stark beschnitten oder geraubt werden.
14.10 Alle if ... else if Konstruktionen müssen mit einem else-Zweig beendet werden.
Die meisten if-Anweisungen haben auf natürliche Weise gar keinen Bedarf an einem else-Zweig. Es ist Unfug, zu verlangen, daß in einer Quelle hunderte solcher Zweige mit einer Leeranweisung (siehe 14.3) stehen, wenn es niemals sinnvollen false-Code geben kann. Warum sollte hier if (Sig) ErrXI(2, Sig); ein else-Zweig folgen?
15.3 Der letzte Absatz einer switch-Anweisung muß der default-Absatz sein.
Dadurch wird in diesem Kontext die Sicherheit reduziert! Es ist öfter sinnvoll und besonders sicher, den default-Absatz als ersten zu plazieren und ihn (beispielsweise) auf einen Initialisierungs-Absatz durchfallen zu lassen. Das Durchfallen ist allerdings durch Regel 15.2 verunmöglicht. Es ist zwar sinnvoll, einen default: zu erzwingen. Aber wieso unbedingt als letzten Absatz?
16.1 Funktionen mit einer variablen Anzahl von Argumenten dürfen nicht definiert werden.
Solche Funktionen sind im Regelfall Problemlöser mit hoher Übersichtlichkeit. Als Ersatz können mehrere bis viele Funktionen mit einer steigenden Anzahl und/oder unterschiedlichem Typ von Argumenten implementiert werden, oder es wird an jeder Aufrufstelle zuvor ein Argumente-Array gefüllt und übergeben. Vermeidungs-Code ist also - eigentlich wie immer - unelegant, aufwendiger und unübersichtlich.
16.2 Funktionen dürfen nicht sich selbst aufrufen, weder direkt noch indirekt.
Rekursivität ist ein gewaltiges Sprachmittel mit hoher Eleganz, Einfachheit und Übersichtlichkeit. Nichtrekursiver Vermeidungs-Code ist im Regelfall zwei- bis dreimal aufwendiger und unsicher(er), da alles, was bei Rekursion automatisch im Hintergrund abläuft, manuell nachgebildet werden muß, wobei die maximal abzuspeichernde Kontrolldatenmenge vorher nicht bekannt ist und mathematisch bestimmt werden muß, sofern das möglich ist. Wenn eine eindeutige Vorherbestimmung nicht möglich ist, muß ausgiebig getestet werden und danach das Doppelte angesetzt werden, das gegen Überlauf abgesichert werden muß. Auch hier bewirkt eine MISRA-Regel folglich das Gegenteil von dem, was MISRA eigentlich erreichen will.
17.4 Indexierter Zugriff auf ein Array ist die einzige erlaubte Art von Zeigerarithmetik.
Zeigerarithmetik ist ein zu starkes Sprachmittel als daß es so sehr zurückgedrängt werden sollte. Mindestens Inkrement und Dekrement per ++ -- sollte zusätzlich erlaubt sein. Die Vorteile von Zeigerarithmetik sind eminent, so daß Programmierer sie erlernen sollten. Zeigerarithmetik verbieten und die Programmierer in diesem Punkt inkompetent zu belassen, ist der falsche Weg.
18.1 Alle Struktur- und Union-Typen müssen am Ende der Übersetzungseinheit komplett sein.
Unklar. Genaue Kenntnis des C-Standard erforderlich.
18.2 Ein Objekt darf nicht einem überlappenden Objekt zugewiesen werden.
Unklar. Überlappt das Objekt vor oder nach der Zuweisung? Objekte können einander nicht zugewiesen werden, Werte von Objekten schon. Regelprüfprogramme verwenden diese Regel nicht.
18.3 Ein Speicherbereich darf nicht für beziehungslose Zwecke verwendet werden.
Unklar. Regelprüfprogramme verwenden diese Regel nicht.
18.4 Unionen dürfen nicht verwendet werden.
In genau spezifiziertem Kontext können Unionen außerordentlich hilfreich sein und die Übersichtlichkeit und Sicherheit erhöhen. Microcontroller werden von Compiler-Herstellern unterstützt, auch indem ihre Register und Registerteile durch Unionen zugänglich gemacht (gemappt) werden. Diese Aufgabe selbst ohne Unionen durchzuführen, mittels z.B. 4000 Ausdrücken unter jeweiliger Verwendung von & | >> << (cast) wäre ganz sicher unsicherer und sehr unvernünftig.
19.6 #undef darf nicht verwendet werden.
Die Compiler-Option -U für den gleichen Zweck wurde gewiß nicht grundlos implementiert. Wenn #undef innerhalb eines wohldefinierten Konzeptes verwendet wird, ist nichts dagegen einzuwenden. Makros aus dem Include-Verzeichnis (oder vergleichbar) sollten allerdings nicht wegdefiniert werden. Dieses Verbot kann die Sicherheit beschädigen! Es kann nämlich einen Objektnamen geben, der gleichlautend mit einem Makronamen ist und der ohne vorhergehende Entdefinition des Makros unabsichtlich expandiert wird.
19.12 Die Präprozessor-Operatoren # oder ## dürfen höchstens einmal vorkommen in einer Makro-Definition.
Es kommt nicht selten vor, daß ## vielfach benötigt wird, wenn vielfach vorkommende Variablennamen mit einheitlichem Namensteil hergestellt werden müssen. Und wenn dieser Operator 100-mal in einer Definition vorkommt - was ist so gefährlich daran? Eine einfachere Funktion eines Operators ist doch kaum vorstellbar.
20.4 Dynamische Heap-Speicher-Allokation darf nicht verwendet werden.
Es wurden und werden viele Programme realisiert, die ohne malloc() und Co. gar nicht machbar wären. Beispielsweise ein Skriptinterpreter könnte bei manchen Verwendungen temporär 500 MiB Speicher benötigen, im Regelfall jedoch nur 40 KiB. Es wäre sehr unvernünftig, in einem solchen Programm pauschal statische Arrays von insgesamt 1000 MiB Größe zu definieren! Ebenso unvernünftig wäre es, statisch nur 200 KiB zur Verfügung zu stellen. Zudem ist unbekannt, welche Größen die Arrays jeweils später zur Laufzeit haben müssen. Eine andere Art von Programm auf einem Microcontroller könnte während der StartUp-Phase für eine halbe Sekunde 280 KiB Speicher benötigen. Anschließend könnte der Speicherbedarf stark unterschiedlich sein, je nach dem, wie viele und welche Kommunikationsprotokolle vom Kunden per Key freigeschaltet wurden und (gleichzeitig) benutzt werden. Ein Protokoll wie z.B. IEC61850 kann durchaus 500+ KiB benötigen. Compiler-Limits werden mitunter als infinity angegeben. Das bedeutet eine alleinige Abhängigkeit von der auf der jeweiligen Plattform verfügbaren Speichermenge - eben soweit der Speicher reicht. Es wird von vielen Programmen verlangt, daß sie sich arbiträr verhalten. Ganz ohne dynamische Besorgung von Arbeitsspeicher geht es einfach nicht!
20.6 Das Makro offsetof in der Bibliothek stddef.h darf nicht verwendet werden.
Der Offset von Struktur-Mitgliedern läßt sich auch auf andere Weise feststellen. Das ist allerdings aufwendig, unübersichtlich, unsicher und nur zur Laufzeit möglich. Das Verbot erscheint unter diesem Aspekt nicht vernünftig. Oder soll das Verbot bedeuten, daß ein Offset grundsätzlich nicht festgestellt werden soll?
20.7 Das Makro setjmp und die Funktion longjmp dürfen nicht verwendet werden.
Dieses Sprachmittel ist mächtig und elegant. Mit Hilfe dieses Sprachmittels sind die gewünschten Verhaltensweisen eines Programms schnell, übersichtlich und sehr einfach herstellbar. Oft können Sicherheitsanforderungen genau dadurch erfüllt werden. Ohne dieses Sprachmittel sind gleiche Verhaltensweisen eines Programms nur mit sehr viel größerem Aufwand herstellbar. Zumeist sind andere oder zusätzliche Konzepte vonnöten. Es ist zu bezweifeln, daß der Verzicht auf dieses Sprachmittel sicherere Programme bewirkt.
20.8 Die Signal-Verarbeitung in signal.h darf nicht verwendet werden.
Auf Microcontrollern (ohne Betriebssystem) wird man diese Signale nicht benötigen. Aber ansonsten ist dies die einzige Möglichkeit, beispielsweise ein Programm Aufräumarbeiten erledigen zu lassen, wenn ein Terminierungssignal empfangen wurde. Programme, die eigentlich Signale bräuchten, aber ohne Signale arbeiten (müssen), produzieren oft unerwünschten Schrott im \TEMP-Verzeichnis.
20.9 Die Eingabe-/Ausgabe-Bibliothek stdio.h darf in Produktions-Code nicht verwendet werden.
Die Funktion snprintf kann allerdings außerordentlich nützlich sein! Es werden auch selbstentwickelte snprintf verwendet, die weitere Formate unterstützen, beispielsweise zentrierte Ausgabe (:), Auffüllen mit Leerzeichen ($), Ausgabe mit Gleitpunkt bei Integer (,w.n), etc. Bei Microcontrollern benutzen Funktionen wie Print_lcd und Print_uart die Funktion snprintf. Die Verwendung des printf-Formats ist einfach, übersichtlich und universell. Viele Compiler warnen (bei stdio-Funktionen), falls das Format nicht zu den Argumenten paßt.