Diskussion:MISRA-C

aus Wikipedia, der freien Enzyklopädie
Letzter Kommentar: vor 1 Jahr von 84.160.201.84 in Abschnitt Alternative
Zur Navigation springen Zur Suche springen

Inhalt

[Quelltext bearbeiten]

Der Artikel ist noch zu dürftig - also eine Übersicht der Regeln wäre gut. In der Literatur ist zwar auf Schellong's Buch verwiesen mit dem Hinweis auf eine Kritik, die sollte hier aber auch umrissen werden.

Das erübrigt sich wohl, denn dieser Literatureintrag wurde von O.Koslowski entfernt, mit dem Hinweis, daß 9 Seiten Kritik in einem Buch als Literatureintrag ungeeignet sind. Sind 8 Seiten geeignet?, oder vielleicht 5?--WPmaglite (Diskussion) 16:17, 7. Jun. 2012 (CEST)Beantworten

Ja, dieser Artikel reizt zum Widerspruch - z.B.:

[Quelltext bearbeiten]

Schreibt man hier von vorn herein Typsicherheit vor, (nach C++ Vorbild) sieht das ganze gleich deutlich besser aus und es kommt gar nicht erst zu solchen Problemen

1. Compiler-unabhängige Typdeklaration damit man wirklich mit einem BOOL arbeiten kann um Trivialitäten wie "x == TRUE" zu vermeiden

typedef BOOL int /* könnte jetzt auch char oder ein Bit im Bitfeld oder sonstwas sein, nur eben eindeutig */

#define TRUE (( BOOL ) 1 == ( BOOL ) 1)

#define FALSE (! TRUE) /* oder auch (( BOOL ) 1 != ( BOOL ) 1) */

/* 2. jetzt kann man eindeutig und sicher schreiben: */

BOOL i = ( BOOL ) a;

if ( i )

{

/* Anweisung */

}


/* Auch der inverse Vergleich ist jetzt schöner. Dafür wurde in C */

/* der geniale !-Operator für BOOLsche Invertierung erfunden. */

if ( ! i )

{

/* Anweisung */

}

/* Ein .. */

/* if ( i == FALSE ) */

/* oder gar .. */

/* if ( i != TRUE ) */

/* wie man es oft sieht, ist hier eine völlig überflüssige Bedingungsverdoppelung. */


/* Oder es handelt sich eben wirklich um einen Vergleich von nicht boolschen Typen .. */

int i = a; /* Annahme hier: "int a" ist initialisiert */

if ( i == a )

{

/* Anweisung gegen die jetzt nichts mehr einzuwenden ist. */

/* Hier ist klar was gemeint ist, ein Vergleich von int Werten. */

/* Das Ergebnis des Vergleichs ist, so wie oben, vom Typ BOOL */

}

/* Ein if ( i = a ) ... ist natürlich grundsätzlich nicht akzeptabel! */




Hallo wer auch immer.

Diesen Einwand verstehe ich nicht. Es geht bei MISRA nicht darum, eine neue C-Sprache zu entwickeln, sondern den Umgang mit C sicherer zu machen. Safer C also. Das ist der Auftrag von MISRA. Im übrigen schreibt MISRA auch die Compiler-unabhängige Typdeklaration vor.

Doch Achtung: auch die Auflösungen auf der Seite sind nicht ganz MISRA konform!

Es muss heißen:

i = a; if (TRUE == i)

und nicht

if (i == TRUE).

Damit will MISRA ebenfalls eine solche versehentliche Zuweisung vermeiden. Vergisst der Programmierer ein = so wird der Kompiler nun ein Fehler ausspucken!

--Any nick 02:49, 30. Jan. 2008 (CET)Beantworten

Hoffentlich arbeitest du nicht in der Qualitätssicherung ;-) MISRA-C unterscheided zwischen int-Typen und "effektiv booleschen" Typen. Um einen int-Ausdruck in einen booleschen Ausdruck umzuwandeln, ist er gegen "0" zu vergleichen (nicht gegen "FALSE"). Gegen "TRUE" vergleichen ist natürlich richtig schlimm. Für boolesche Ausdrücke sind nur die Operatoren "||", "&&" und "!" erlaubt, das schließt jegliche Vergleichsoperatoren aus. Wenn also deine Variable "i" ein int ist, dann muss der Ausdruck "if (i != 0)" heißen. Ist es jedoch von effektiv booleschem Typ, ist "if (i)" richtig. Leider kennen viele Werkzeuge zur MISRA-Prüfung den effektiv booleschen Typ nicht und bestehen auf einen Vergleich von bool-Variablen (z.B. QAC). Die Lint-Derivate sind dabei eine rühmliche Ausnahme. Dieses unnatürliche Vertauschen (TRUE==i) von Variablen und Konstanten ist übrigens keine MISRA-Regel, kommt allerdings in den Style-Guides vieler Firmen vor. Das gewöhnt man sich aber schnell ab, wenn man in C++ etwickelt (Stichwort Operatorüberladung) --84.146.83.81 11:54, 28. Jul. 2010 (CEST)Beantworten


imho sind dieser Artikel und vor allem die Code-Beispiele wenig informativ. Dass das zweite Beispiel sicherer ist als das erste ist doch offensichtlich. Weniger offensichtlich ist der Zusammenhang zu MISRA. Dem ganzen wird doch sicher (mindestens) eine Regel aus der "Liste von über 100 Programmierregeln" zu Grunde liegen. Diese sollte man schon zitieren, oder darf man das aus urheberrechtlichen Gründen nicht?


"Beim folgenden Beispiel kommt es auf den Compiler an, wann er i++ (entspricht i = i + 1) ausführt" Diese Aussage ist falsch. i++ ist ein post-increment. Das heisst, der Ausdruck wird erst ausgewertet, und dann wird i incrementiert. Der Wert von i++ is der Wert vor dem Increment. Andernfalls waere das pre-increment (++i) zu verwenden. Bei einer Verwendung als Funktionsparameter (auch für ein korrekt geschriebenes macro) ist der Wert von i++ compilerunabhängig eindeutig. -- JM (nicht signierter Beitrag von 84.189.46.247 (Diskussion) 15:34, 25. Nov. 2011 (CET)) Beantworten


Nachfolgend gilt Standard MISRA-C:2004, der sich auf C90 bezieht.[1]

Es mag sein, dass der C-Standard stellenweise Raum für Interpretationen lässt - der MISRA-Standard übertrifft wohl den C-Standard in dieser Hinsicht. Eine größere Anzahl von Regeln ist mit zu wenigen Worten unklar, missverständlich, verwirrend festgelegt worden.

2.3 Kommentare dürfen nicht verschachtelt werden.
Das ist korrekt; Kein C-Standard definiert 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 mehrfacher Hinsicht. 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 mehrfacher Hinsicht. 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, muss 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 muss 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, muss 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, dass 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.
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 embedded programming ist goto nahezu unverzichtbar, da sehr ressourcenschonend. Es ist nicht sinnvoll, dass 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 musste. 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, dass 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ässlich 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, dass goto verboten ist. Siehe auch 14.5. Sehr oft muss programmiert werden, dass 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, dass man sich eine geeignetere Programmiersprache aussucht. Andere Programmiersprachen erlauben ebenfalls die beliebige Verwendung einer return-Anweisung. Von dort ist allerdings nicht bekannt, dass 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, dass 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 muss 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 unsicherer, da alles, was bei Rekursion automatisch im Hintergrund abläuft, manuell nachgebildet werden muss, wobei die maximal abzuspeichernde Kontrolldatenmenge vorher nicht bekannt ist und mathematisch bestimmt werden muss, sofern das möglich ist. Wenn eine eindeutige Vorherbestimmung nicht möglich ist, muss ausgiebig getestet werden und danach das Doppelte angesetzt werden, das gegen Überlauf abgesichert werden muss. 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 dass 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 dass 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 gewiss 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.
19.12 Die Präprozessor-Operatoren # oder ## dürfen höchstens einmal vorkommen in einer Makro-Definition.
Es kommt nicht selten vor, dass ## 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, dass 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ässt 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, dass 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, dass 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.

Die MISRA-Regeln sind entstanden aufgrund von Fehlern, die von C-Programmierern gemacht wurden. Diese Zusammenstellung ist ein Verdienst von MISRA. Die Art der aktuellen Verwendung dieser statistischen Resultate ist jedoch im Endeffekt wahrscheinlich nicht nützlich, sondern schädlich.

Wenn diese lange Liste von MISRA-Regeln (40 von über 140) mit C-Sachverstand gelesen wird, kommt zuerst Verwunderung auf, die nach einer Weile des Lesens in blankes Entsetzen umschlägt. Es ist durchaus möglich, über 30 Jahre C-Programmierung zu betreiben und nicht einen einzigen Fehler zu begehen, der im Zusammenhang mit irgendeiner MISRA-Regel und deren Begründung steht! Das heißt, es ist niemals ein Fehler passiert, aufgrund von mangelndem Verständnis eines Sprachmittels der Programmiersprache C. Es wurden eben - ganz einfach - Sprachmittel solange nicht eingesetzt, solange sie nicht vollkommen verstanden waren. Oder die Sprachmittel wurden nur insoweit ausgeschöpft wie es zum jeweiligen Kenntnisstand paßte. Fehler anderer Art sind durchaus zu Hunderten passiert. Nämlich Algorithmus-Fehler: Es wurden (kreative) Überlegungen vergessen oder falsch angestellt. Und genau zur Minimierung von Algorithmus-Fehlern ist das vollständige Ausschöpfen aller Sprachmittel geeignet.

Die Erschaffer von Programmiersprachen haben ganz sicher jedes Sprachmittel genauestens überlegt, weil sie wissen, was wie benötigt wird, um im jeweiligen Rahmen alle Programmierprobleme möglichst zügig, effizient, elegant und übersichtlich lösen zu können. Bei Befolgung aller MISRA-Regeln jedoch wird die Sprache C so stark reduziert, daß es sich nicht mehr um eine leistungsfähige Programmiersprache handelt. Es müssen schlechtere Konzepte verwendet und der Quellcode unübersichtlich aufgeblasen werden, um die fehlenden Sprachmittel zu ersetzen. Es fehlt der Raum für Kreativität - man kämpft mit dem Wust. Es ist zu bezweifeln, daß dies zu sichereren Programmen führt. Außerdem sind solche Programme zur Laufzeit oft beträchtlich weniger performant, bis hin zur Unbrauchbarkeit.

Die Sprachen ADA und PEARL werden für sichere Programmierung empfohlen. Beide Sprachen stellen beispielsweise RETURN; als auch RETURN (expr); zur Verfügung, mit den gleichen vielfältigen Verwendungsmöglichkeiten wie in C. ADA enthält auch goto und exit (break), wobei eine exit-Anweisung auch aus beliebig vielen verschachtelten Schleifen herausspringen kann. Jedoch hier gibt es keinerlei Verbote, um Sicherheit zu gewährleisten. Diese Sprachen sind für sichere Programmierung geeignet, unter anderem gerade wegen ihrer vorhandenen Sprachmittel.

Es ist grundlegend, daß die Wahrscheinlichkeit von Softwarefehlern mit der Gesamtmenge und mit den Teilmengen an Codezeilen steigt. Je komplexer und unübersichtlicher ein Quellcode(teil) ist, desto wahrscheinlicher sind Fehler darin. Umgekehrt ist schlanker und übersichtlicher Code potentiell sicherer. Des Weiteren waren in der Vergangenheit weit überwiegend Algorithmus-Fehler (versäumte oder falsche Überlegungen) Ursache von Schäden durch fehlerhafte Software (Bugs, Ariane). Die Befolgung aller MISRA-Regeln verursacht (indirekt) potenziell unsicherere Software. Verwender unterliegen dem gefährlichen Trugschluß, daß ihre Software allein durch Befolgung der MISRA-Regeln sicher oder sicherer ist. Verwendungsverbot von Sprachmitteln ist hier ein falscher Ansatz. Die stärkste positive Wirkung bezüglich Sicherheit wird erzielt, wenn beim Schreiben eines jeden Codeabschnittes dessen Aufgaben und Wechselwirkungen gründlichst von einem erfahrenen und talentierten Programmierer überlegt und berücksichtigt werden und dieser Programmierer die verwendete Programmiersprache meisterhaft beherrscht und verwendet und die Hardware-Plattform sehr gut kennt. Oje-Effekt

  1. http://supp.iar.com/FilesPublic/UPDINFO/004916/arm/doc/EW_MisraC2004Reference.ENU.pdf

(nicht signierter Beitrag von Der Zampano (Diskussion | Beiträge) 01:40, 3. Jul 2012 (CEST))

Kritik an der Kritik

[Quelltext bearbeiten]

Deine Kritik hätte viel kürzer ausfallen können, wenn du die MISRA2004 wenigstens einmal gelesen hättest. Dann wären nämlich die als "unklar" gekennzeichneten Punkte weggefallen. Die Regeln, welche du zitierst, stellen in der MISRA2004 nämlich nur die Kapitelüberschriften dar, Sinn und Zweck ist danach meistens ausführlich erläutert. Ich stimmer dir vollkommen zu, dass einige Regeln zu streng und nicht mehr zeitgemäß sind, allerdings ist die MISRA2004 ja auch schon wieder 10 Jahre alt! Z.B. das Verbot von "//"-Kommentaren war 2004 noch durchaus sinnvoll, da diese Form erst 1999 in den C-Standard aufgenommen wurde, 2012 wurde das Verbot aber entfernt.

Du darfst das Regelwerk auch nicht als persönliche Anfeindung und Beschneidung deiner künstlerischen Freiheit verstehen, sondern als Richtlinien, die ausschließlich der Portabilität, Lesbarkeit und Vermeidung häufiger (!) Fehlerquellen von Quelltexten dienen. MISRA gibt selbst genügend Beispiele, in denen ein Abweichen von den Regeln sinnvoll ist, sagt aber auch, dass diese Abweichungen der Qualitätskontrolle des jeweiligen Projektes unterliegen müssen und ausreichend dokumentiert sein sollen.

Ich könnte jetzt wirklich zu fast allen deinen Kritikpunkten etwas schreiben, allerdings steht das alles auch schon in MISRA2004 und MISRA2012. Zu den ersten Punkten deiner Liste:

2.3 Kommentare dürfen nicht verschachtelt werden.
Das ist korrekt; Kein C-Standard definiert verschachtelte Kommentare.
Zustimmung!
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.
Wie bereits geschrieben: Ein Standard, der erst 5 Jahre alt ist, kann nicht die Basis eines Regelwerkes sein, welches sich Portabilität auf die Fahne geschrieben hat.
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.
Hast du mal darüber nachgedacht, was beim Auskommentieren von Code mit Kommentaren passiert? Richtig, Verstoß gegen 2.3 (keine Verschachtelung von Kommentaren). MISRA sagt: Verwende lieber "#if 0".
5.5 Kein Objekt- oder Funktions-Identifizierer mit statischer Speicherdauer sollte wiederbenutzt werden.
Unklar in mehrfacher Hinsicht. Regelprüfprogramme verwenden diese Regel nicht.
Überhaupt nicht unklar. Es geht hier darum, dass globale Objekte nicht den selben Namen haben dürfen, wie statische Objekte. Regelprüfprogramme testen das: QAC-Regeln 1525 bis 1529.
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 mehrfacher Hinsicht. Genaue Kenntnis des C-Standard erforderlich. Regelprüfprogramme verwenden diese Regel nicht.
Das ist glasklar: um Verwechslungen zu vermeiden, sollst du z.B. in einer Funktion keine lokale Variable definieren, die genau so heißt, wie eine gleichzeitig zugreifbare Variable in einem anderen Namensraum, z.B. in einer globalen Strukturvariablen. Diese Regel ist in MISRA2012 nicht mehr enthalten, deine Kritik wurde wohl gehört ;-) Regelprüfprogramme testen das: QAC-Regeln 0780 und 0781.
5.7 Kein Identifizierer-Name sollte wiederbenutzt werden.
Inwiefern nicht? Vollkommen unklar! Regelprüfprogramme verwenden diese Regel nicht.
Ist wieder in MISRA2004 erklärt: zwei unterschiedliche Strukturen sollen keine Elemente mit gleichem Namen haben. Hier hast du wieder meine Zustimmung, das ist zu streng. MISRA2012 hat diese Regel entfernt, QAC testet das auch nicht (dieses eine Mal hast du Recht).
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.
Da die Basistypen völlig plattformspezifisch sind, ist ein kompletter Bann sinnvoll. Für Int-Typen gilt nur: char <= short <= int <= long. Das kann auch heißen, dass von char bis long alles gleich lang ist. Gibt's nicht? Dann kennst du die TMS320-Signalprozessoren nicht. Da ist sogar char 32 bit. Ein Feld mit 4 chars zu definieren und anzunehmen, es wäre 4 Byte lang, wäre verhängnisvoll. Wenn der originale Quelltext aber ein uint8 benutzt, auf der Plattform dies aber nicht definiert ist, führt das zu einem Compilerfehler statt zu merkwürdigem Verhalten.
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.
Wo ist egal, sobald mehr als eine Deklaration existiert, müssen die Typen kompatibel (nicht gleich) sein. MISRA2004 verweist dazu auf ISO9899.
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.
Dazu lasse ich einfach mal MISRA2004 sprechen: "Normally this will mean declaring an external identifier in a header file, that will be included in any file where the identifier is defined or used." Unklar, was daran unklar sein soll. Außerdem würde mich interessieren, was du da als Regelprüfprogramm verwendest, scheint Schrott zu sein. QAC-Regeln 1513, 3222, 3408, 3447, 3451.

Es gibt viele MISRA-Regeln, die selbst erfahrene Entwickler nicht gleich verstehen. Warum muss ich z.B. nach Schiebeoperationen und Invertieren einen Cast auf den Zieltyp machen? Frag doch mal deine Kollegen, was beim bitweise negieren einer uint16-Variable rauskommt. Ist das positiv oder negativ? Viel schlimmer, auf einer 16-Bit-Plattform positiv, auf einer 32-Bit-Plattform negativ! Ich schätze mal, dass das 10% der Entwickler wirklich wissen. MISRA ist dafür da, dass die restlichen 90% nicht zu viel kaputtmachen können, dafür verzichte ich gern auf ein paar künstlerische Freiheiten beim gestalten meiner Quelltexte. --87.170.41.207 15:40, 4. Feb. 2014 (CET)Beantworten

magic numbers

[Quelltext bearbeiten]

Was auch immer die Intention gewesen sein mag, das Beispiel passt zu magic numbers in keiner Weise. -- Polluks 16:20, 21. Dez. 2017 (CET)Beantworten

Alternative

[Quelltext bearbeiten]

Eine berechtigte Frage ist wohl, ob nicht stattdessen besser Rust (Programmiersprache) verwendet werden sollte. Wer hat etwas Fundiertes dazu? 84.160.201.84 12:53, 28. Aug. 2023 (CEST)Beantworten