Es handelt sich um einen Teil eines Algorithmus, der
Zeichenketten "-32767" bis "32767" in einen 16 Bit breiten Integer umwandelt.
Der Code ist ultra-schnell und muß das auch sein.
1 2 /* Misra-konformer Code: */ 3 4 const unsigned char *a; 5 int i=0; 6 /* ... */ 7 if (F_DIG(a[0])) { 8 if (F_DIG(a[1])) { 9 if (F_DIG(a[2])) { 10 if (F_DIG(a[3])) { 11 if (F_DIG(a[4])) { 12 i+= d4[a[0]-'0']; 13 i+= d3[a[1]-'0']; 14 i+= d2[a[2]-'0']; 15 i+= d1[a[3]-'0']; 16 i+= a[4]-'0'; 17 } 18 else { 19 i+= d3[a[0]-'0']; 20 i+= d2[a[1]-'0']; 21 i+= d1[a[2]-'0']; 22 i+= a[3]-'0'; 23 } 24 } 25 else { 26 i+= d2[a[0]-'0']; 27 i+= d1[a[1]-'0']; 28 i+= a[2]-'0'; 29 } 30 } 31 else { 32 i+= d1[a[0]-'0']; 33 i+= a[1]-'0'; 34 } 35 } 36 else { 37 i+= a[0]-'0'; 38 } 39 } 40 else { 41 ; 42 } 43 if (m) { i= -i; } 44 else { ; } 45 return (i); 46 47 /* Nicht misra-konformer Code: */ 48 49 const unsigned char *a; 50 int i=0; 51 // ... 52 if (!F_DIG(a[0])) goto ADD0; 53 if (!F_DIG(a[1])) goto ADD1; 54 if (!F_DIG(a[2])) goto I2; 55 if (!F_DIG(a[3])) goto I3; 56 if (!F_DIG(a[4])) goto I4; 57 goto I5; 58 I2: a+=1; goto ADD2; 59 I3: a+=2; goto ADD3; 60 I4: a+=3; goto ADD4; 61 I5: a+=4; 62 ADD5: i+= d4[a[-4]-'0']; 63 ADD4: i+= d3[a[-3]-'0']; 64 ADD3: i+= d2[a[-2]-'0']; 65 ADD2: i+= d1[a[-1]-'0']; 66 ADD1: i+= a[ 0]-'0'; 67 ADD0:; 68 return (m?-i:i); 69 70 /* -------------------------- */ 71
Es ist eindeutig erkennbar, daß der misra-konforme Code wesentlich größer und unübersichtlich ist.
Er ist mehrfach verschachtelt und muß Code mehrfach verwenden.
Allein die drei Schlußzeilen sind ein erzwungener Krampf.
Insbesondere, wenn der Zahlenbereich von 5 auf 10 oder 19 Stellen erweitert wird, steigt die
Größe des Code überproportional an. Die Größe ist eine Potenz der Stellenanzahl.
Hingegen der nicht-konforme Code ist wesentlich kürzer und kompakter und garnicht verschachtelt.
Seine Größe ist stets etwa das dreifache der Stellenanzahl. Codeteile werden nicht mehrfach
verwendet und es liegt eine übersichtliche tabellarische Ordnung vor.
Zusätzlich ermöglichen die goto-Label sprechende Label-Bezeichner.
Der misra-konforme Code ist keineswegs sicherer - nein, er ist eindeutig unsicherer!
Je mehr Zeilen ein Code umfaßt, desto unsicherer ist er.
Das ist ein absolut unerschütterlicher und bekannter Fakt.
Und misra-konformer Code ist - falls Regeln zur Wirkung kommen - grundsätzlich
größer, unübersichtlicher, umständlicher, oft fragwürdig verkrampft, abscheulich verhunzt, bis hin
zu Idiotie, und damit ganz klar unsicherer!
Weiterhin entsteht unter anderen die Frage, wieso goto verboten werden muß?
Eine goto-Anweisung (goto A; ... A:...;) ist die primitivste Anweisung, die es gibt.
Eine einfache Zuweisung (a=b;) ist bereits wesentlich problematischer.
Wie unschwer (am nicht misra-konformen Code) erkennbar ist, sendet
nachfolgender
Code Kopfdaten des Protokolls HTTP.
1 2 /* Misra-konformer Code: */ 3 4 int send_header(int fd, struct op_t *out, uint len) 5 { 6 static char const LF[]= "\r\n"; 7 unsigned char buf[4096]; 8 size_t p, end; 9 char const *msg, *s; 10 uint16_t code; 11 int32_t n; 12 int r; 13 14 if (out->location) { code=302; msg="FOUND"; } 15 else { code=200; msg="OK"; } 16 17 strcpy(buf, "HTTP/1.0 "); 18 p = strlen(buf); 19 p+= utoa(&buf[p], code); 20 strcpy(&buf[p], " "); 21 p+=1; 22 strcpy(&buf[p], msg); 23 p+= strlen(msg); 24 strcpy(&buf[p], LF); 25 p+= strlen(LF); 26 s = "Content-Length: "; 27 strcpy(&buf[p], s); 28 p+= strlen(s); 29 p+= utoa(&buf[p], len); 30 strcpy(&buf[p], LF); 31 p+= strlen(LF); 32 s = "Content-Type: "; 33 strcpy(&buf[p], s); 34 p+= strlen(s); 35 36 end= sizeof(buf); 37 r = 0; 38 n = p; 39 n = u_copy(buf, n, end, out->content_type); 40 if (n<0) { r= -1; } 41 else { 42 n = u_copy(buf, n, end, LF); 43 if (n<0) { r= -1; } 44 else { 45 if (code==302) { 46 n = u_copy(buf, n, end, "Location: "); 47 if (n<0) { r= -1; } 48 else { 49 n = u_copy(buf, n, end, out->location); 50 if (n<0) { r= -1; } 51 else { 52 n = u_copy(buf, n, end, LF); 53 if (n<0) { r= -1; } 54 else { 55 n = u_copy(buf, n, end, LF); 56 if (n<0) { r= -1; } 57 else { b_write(fd, buf, n); } 58 } 59 } 60 } 61 } 62 else { 63 n = u_copy(buf, n, end, LF); 64 if (n<0) { r= -1; } 65 else { b_write(fd, buf, n); } 66 } 67 } 68 } 69 if (r!=0) { syslog(LOG_ERR, "Script headers too large for buffer"); } 70 else { ; } 71 return r; 72 } 73 74 /* Nicht misra-konformer Code: */ 75 76 #define COPY(str) (p= u_copy(p, end, str)) 77 78 int send_header(int fd, struct op_t *out, uint len) 79 { 80 static char const LF[]= "\r\n"; 81 unsigned char buf[4096], *p, *end; 82 char const *msg; 83 unsigned code; 84 85 if (out->location) code=302, msg="FOUND"; 86 else code=200, msg="OK"; 87 p = buf; 88 p+= sprintf(p, "HTTP/1.0 %u %s%s", code, msg, LF); 89 p+= sprintf(p, "Content-Length: %u%s", len, LF); 90 p+= sprintf(p, "Content-Type: "); 91 end= buf + sizeof(buf); 92 93 if (!COPY(out->content_type)) goto OVERRUN; 94 if (!COPY(LF) ) goto OVERRUN; 95 96 if (code==302) { 97 if (!COPY("Location: ") ) goto OVERRUN; 98 if (!COPY(out->location)) goto OVERRUN; 99 if (!COPY(LF) ) goto OVERRUN; 100 } 101 if (!COPY(LF) ) goto OVERRUN; 102 103 b_write(fd, buf, p-buf); 104 return 0; 105 106 OVERRUN: 107 syslog(LOG_ERR, "Script headers too large for buffer"); 108 return -1; 109 } 110 #undef COPY 111
Die Misra-Regeln verbieten bei diesem Beispiel goto, Funktionen mit variabel
vielen Argumenten, alles in <stdio.h>, Komma-Operator, if ohne else, if und else
ohne { }, Pointer-Arithmetik. Function-like Makros und die Basis-Datentypen
sollen vermieden werden. return muß einmal am Ende einer Funktion stehen.
Der misra-konforme Code ist etwa 2,5-mal größer, weniger performant, umständlich,
unnatürlich verkrampft, unübersichtlich und somit beträchtlich unsicherer
als der nicht misra-konforme Code.
Die Funktion utoa() muß zusätzlich selbst entwickelt werden, da
u.a. sprintf() verboten ist.
Es mußten mehrere zusätzliche Hilfsvariablen angelegt werden.
Die Aufforderung von Misra, Typen mit festgelegter Bitbreite zu verwenden,
zeigt sich hier als weniger geeignet als die Verwendung von
int und unsigned.
Die Ausgabedaten müssen aus vielen kleinen Stücken zusammengeflickt
werden. Das ist erzwungen ekelhafter, häßlicher Code, und besonders fehlerträchtig.
Es sind viele lächerliche Hilfskonstruktionen vorhanden.
Die Misra-Regeln erzwingen Code, der regelmäßig eine Katastrophe ist.
Wer hier sagt, es sollte doch die sicherere strncpy() anstelle
von strcpy() verwendet werden, der ist ein Dilettant.
Dies ist allerdings im misra-konformen Code nur schwer zu erkennen.
Im nicht misra-konformen Code ist dies sofort erkennbar.
Der nicht misra-konforme Code hat einen sehr simplen Kontrollfluß, der
blitzartig erkennbar ist. Der Algorithmus ist sofort und klar erkennbar.
Es gibt hier keine aufgeworfenen Fragen.
Nachstehend eine Kommunikation
mit einem eMail-Server (pop3), die seit
etwa 16 Jahren (auf Anhieb)
makellos funktioniert.
Ohne Syntax-Highlighting sollte sie zwecks besserer Übersichtlichkeit etwas
auseinander gezogen werden;
aber mit hat man den Vorteil, daß der gesamte
Algorithmus ohne Scrollen auf einen Blick sichtbar ist.
151 switch (ml=0, z) { 152 case 0: len= sprintf(Buf, "%s %s\r\n", "USER", User); 153 z=1; break; 154 case 1: len= sprintf(Buf, "%s %s\r\n", "PASS", Pass); 155 z=2; break; 156 case 2: len= sprintf(Buf, "%s\r\n", "STAT"); 157 z=3; break; 158 case 3: sc= sscanf(Buf, "%*s %5d %9d", &nmsg, &sz); 159 if (sc!=2) { r=14; goto Q; } 160 if (nm=0, nmsg<1||get&&Stat) goto Q; 161 len= sprintf(Buf, "%s\r\n", get?"LIST":"UIDL"); 162 z=4; ml=1; break; 163 case 4: if (get) { 164 len= sprintf(Buf, "%s\r\n", "UIDL"); 165 z=5; ml=1; break; 166 } 167 memmove(Buf+(o=520), Buf, nr+1); 168 sz= LineEnd(Buf+o); 169 if (sz<=0) { r=15; goto Q; } 170 write(1, Buf+o, sz); 171 o+=sz; z=6; break; 172 case 5: if (++nm>nmsg) goto Q; 173 len= sprintf(Buf, "%s %d %d\r\n", "TOP", nm, Body), ml=1; 174 z=6; break; 175 case 6: if (get) { z=5; break; } 176 sz= LineEnd(Buf+o); 177 if (sz<5||Buf[o]=='.') goto Q; 178 sc= sscanf(Buf+o, "%5i %71[!-~]", &nm, sid); 179 if (sc!=2) { r=16; goto Q; } 180 printf("%d %s\r\n", nm, sid); 181 o+=sz; 182 if (All||MatchID(sid, C, A)) 183 len= sprintf(Buf, "%s %d\r\n", "DELE", nm); 184 break; 185 case 7: z=8; break; 186 case 8: Q:; 187 len= sprintf(Buf, "%s\r\n", "QUIT"); 188 z=9; break; 189 case 9: z=10; break; 190 } 191 if (len>0) { 192 nw= Write(fd, Buf, len); 193 write(1, Buf, len); 194 if (nw!=len) if (r=17, z<8) goto Q; 195 else len=0; 196 } 197 } 198 return r; 199 } 200
Der vorstehende Code verstößt in fast jeder Zeile gegen mindestens
eine Misra-Regel.
Und das ist auch gut so!
Misra verbietet hier den Komma-Operator, Operator ?:, Pointer-Arithmetik,
if ohne else, if und else ohne { },
goto, Funktionen mit variabel vielen Argumenten, alles in <stdio.h>,
und verlangt default: als letzten Case.
Die Funktionalitäten der Funktionen printf, sprintf und sscanf müssen ersetzt werden.
Als Alternative gibt es aber wohl nur die Familie strtoX() in <stdlib.h>.
Folglich müssen viele Konversionsfunktionen selbst entwickelt werden - viele, Viele, VIELE.
(Aufgabe: Wer kann ein plattformspezifisches double in einen String konvertieren?
in alle Ausgabeformate, die printf kann?)
(Aufgabe: Wer kann "%46[3-6A-Z_a-z$#-]" für vorstehendes Format eine Funktion entwickeln?)
Ein Ausgabe-Layout kann nicht mehr übersichtlich in einer Zeile zusammengesetzt werden, sondern
muß aus Einzelstücken über viele Zeilen hinweg zusammengeflickt werden, mit Hilfe einer
Vielzahl von verschiedenen Funktionen.
goto Q kann nur durch eine Funktion ersetzt werden, die den Code des case 8: enthält.
Die lokale Variable z muß dann bei jedem Funktionsaufruf passend verändert werden.
Buf muß dann zumindest datei-lokal sein, oder die Adresse von Buf muß an die Funktion
übergeben werden. (Function-like Makros sollen vermieden werden!)
Pointer-Arithmetik ist hier leicht vermeidbar, da es keine Pointer-Variablen gibt,
indem z.B. Buf+o in &Buf[o] geändert wird.
Ein solcher Misra-Code soll sicherer sein als normaler C-Code?
Nie und nimmer!
Allein die Entwicklung einer eigenen Funktions-Library als Ersatz der stdio-Funktionen
dürfte mindestens ein 50-faches Fehlerpotential enthalten, im Vergleich zum
einfachen Verwenden von fertigen, erprobten, wohldokumentierten Standard-Funktionen.
Und die Anwendung der Funktionen aus der eigenen Library enthält ein
gestiegenes Fehlerpotential im Vergleich zur Anwendung der übersichtlichen
Standard-Funktionen, die meist ein simpler Einzeiler sind.
Die ersten beiden Code-Abschnitte ergeben mit bestimmten Compilern
vollkommen identische Objekt-Dateien: cmp Input1.o Input2.o.
Das beweist, das der Algorithmus vollkommen identisch ist.
Misra-konform ist das jedoch nicht mehr möglich.
1 2 /* Nicht misra-konformer Code: */ 3 4 int Input(int ityp) 5 { 6 register int c; 7 while ( PSnu=0, c= List(ityp), ityp&ITYP_I?++KDOnu:0, 8 !O['t'] && 9 (c<EoF||(ityp&ITYP_I)&& 10 (c>ADD+8&&(!(G.ityp&ITYP_P)||c!=rETURN) 11 ||c==EoF&&(O['I']|O['P']) 12 &&(write(2,"Benutze exit" NL,13+NLSZ), 13 --KDOnu, 1)) 14 ) ); 15 return c; 16 } 17 18 /* Nicht misra-konformer Code */ 19 // Vollkommen identischer Algorithmus. 20 // Eine zufällige Annäherung an Misra-Regeln. 21 22 int Input(int ityp) 23 { 24 register int c; 25 while ( PSnu=0, c=List(ityp), 1 ) { 26 if (ityp&ITYP_I) ++KDOnu; 27 if ( O['t']) break; 28 if ( c>=EoF) { 29 if ( !(ityp&ITYP_I) ) break; 30 if ( c<=ADD+8 || (G.ityp&ITYP_P) && c==rETURN ) { 31 if ( c!=EoF || !(O['I']|O['P']) ) break; 32 write(2, "Benutze exit" NL, 13+NLSZ); 33 --KDOnu; 34 } 35 } 36 } 37 return c; 38 } 39 40 /* Misra-konformer Code: */ 41 42 int Input(int ityp) 43 { 44 register int c; 45 int brk=0; 46 while ( 1 ) { 47 PSnu=0; c=List(ityp); 48 if ((ityp&ITYP_I)!=0) { ++KDOnu; } 49 else { ; } 50 if (O['t']==0) { 51 if (c>=EoF) { 52 if ((ityp&ITYP_I)!=0) { 53 if ( c<=ADD+8 || ( (G.ityp&ITYP_P)!=0 && c==rETURN ) ) { 54 if ( c==EoF && (O['I']|O['P'])!=0 ) { 55 write(2, "Benutze exit" NL, 13+NLSZ); 56 --KDOnu; 57 } 58 else { brk=1; } 59 } 60 else { ; } 61 } 62 else { brk=1; } 63 } 64 else { ; } 65 } 66 else { brk=1; } 67 if (brk!=0) break; 68 } 69 return c; 70 } 71
Es ist frappierend, daß die erste Variante weder if,
else, break noch continue enthält.
Die misra-konforme Variante ist - wie immer - die mit Abstand größte und unnatürlich
verbogene, gestelzte, gewundene, affige, geschraubte und somit die unsicherste und am schwersten durchschaubare Variante.
Misra-Konformität erzwingt tiefstmögliche Verschachtelung und die Benutzung
der in if-else enthaltenen impliziten goto.
Die hier grauenhafteste erzwungene
Konstruktion ist die Verwendung einer Hilfsvariablen brk.
Dadurch ist der Kontrollfluß nicht mehr derselbe, obwohl
das Resultat des Algorithmus dasselbe ist.
Else-Zweige wie der in Zeile 49 sind affig.
Das Argument, es könnte da ja mal was eingetragen werden, ist affig
und zeugt von ungenügender Erfahrung.
Es wird dort bis in alle Ewigkeit keine sinnvollen else-Zweige geben!
Und falls es an anderer Stelle (0,002%) doch mal zutrifft, ist das keine
Berechtigung 99,998% else-Zweige sinnlos zu erzwingen. Affig eben.
Der folgende Code-Abschnitt ist nicht misra-konform (u.a. 4 x ##):
1 # define ISUM(N,STRUCT,I) \ 2 if (TBT._1s==N) { \ 3 for (q=MQ_K##I; q<MQ_K##I+MQ_nK##I; ++q) { \ 4 if (m=Mess.Z.q_m[q], !m) continue; \ 5 v= (UNS4)STRUCT.K[q-MQ_K##I].I; \ 6 do *(MESW*)MessPtr[m-1].m= (MESW)v, \ 7 Mess.oflg[m-1]= 1; \ 8 while ((m=Mess.m_m[m-1])!=0); \ 9 } \ 10 } 11 ISUM(17,Batt,i) 12 ISUM(18,Sys,ig) 13 ISUM(19,Sys,il) 14 ISUM(20,PAC,uac) 15 ISUM(21,Sys,id) 16 ISUM(22,REC,irec) 17 ISUM(23,PAC,ipac) 18 ISUM(24,DCC,idcc) 19 ISUM(25,PDC,ipdc) 20 # undef ISUM
Hauptsächlich aus Sicherheitsgründen wurde hier ein Makro verwendet.
Die Tabelle am Ende ist eindeutig und zweifelsfrei wesentlich
übersichtlicher und sicherer als
wenn der definierte Makro-Inhalt dort expandiert 9-mal hintereinander stünde,
mit den jeweiligen Unterschieden.
Misra-Regel 19.12 verbietet die mehrfache Verwendung des Makro-Operators ##.
Folglich bewirkt erneut eine Misra-Regel unsichereren Code.
Das ist das Gegenteil dessen, was Misra (behauptet) erreichen will!
Funktionsaufrufe scheiden hier aus Performance-Gründen aus; der Code
ist Bestandteil eines Interrupt-Handlers.
Hier wurde darauf verzichtet, eine misra-konforme Variante zu zeigen.
Die Tabelle müßte zwei weitere Argumente bzw. Spalten erhalten:
MQ_Kirec, MQ_nKirec, etc.
Sie würde dadurch komplexer und fehlerträchtiger.
Desweiteren würde das hier aus Sicherheitsgründen angewandte Konzept, übereinstimmende
Namensteile zu verwenden (MQ_Kirec, REC.K[].irec), unterlaufen.
Eine Katastrophe wäre misra-konformer Code hier nicht, aber: function-like Makros()
sollen ohnehin vermieden werden (19.7), Die Operatoren # ## sollen ohnehin
vermieden werden (19.13), #undef ist verboten (19.6), Definition
eines Makros innerhalb eines Blockes ist verboten (19.5).
Der vorstehende Code verstößt gegen überraschend viele Misra-Regeln, was ihn allerdings
sicherer macht. Beispiel: #undef ISUM verhindert eine unabsichtliche
Expansion eines nachfolgend verwendeten Objektnamens ISUM, den es ja geben könnte.
Ein definiert begrenzter Gültigkeitsbereich eines Makros kommt doch dem sehr vorteilhaften und stark sicherheitserhöhendem Konzept von Modularisierung und Kapselung entgegen.
Es ist schleierhaft, warum jemand dagegen wirkt, der sich auf die Fahnen geschrieben
hat, für mehr Sicherheit in der C-Software zu sorgen?!
Nachfolgend eine rekursive Funktion, die beliebig angeordnete auch verschachtelte Kommentare identifiziert und deren Inhalte separiert ausgibt. Kommentare können direkt aneinanderstoßen und auch leer sein. Vorderteile, Zwischenteile und Endteile (s.u. aaa) dürfen beliebig fehlen. Kommentar-Syntax /* oder */ darf fehlerhaft beliebig fehlen. In Zeile 6 kann ein Funktionsaufruf hinzugefügt werden, der die Kommentarsyntax im String s auf Korrektheit prüft: Die Anzahlen von /* und */ müssen gleich sein und die Anzahl von */ darf nie höher sein als die Anzahl von /*. (Typische Misra-Programmierer werden öfter die letzte Bedingung weglassen.)
1 2 const char *Kommentar(int e, const char *s) 3 { 4 char buf[200]; 5 unsigned b=0; 6 if (!s) return 0; 7 do { 8 while (s[0]=='/'&&s[1]=='*') s= Kommentar(e+1, s+2); 9 if (s[0]==0 || s[0]=='*'&&s[1]=='/'&&(s+=2, 1)) { 10 if (b||e>0) buf[b]=0, printf("%d: %s\n", e, buf); 11 break; 12 } 13 buf[b++]= *s++; 14 } 15 while (b<sizeof(buf) || (b=sizeof(buf)-1, 1)); 16 return s; 17 } 18 19 //a.out 'aaa/*bbbbbbb/*ccc*/bbbbb*/aaaaaaa/*ddddd/*ee/*f/*/**/*/f/*g*//*h*/f*/ee*/dd*/aaaaaaaaaa' 20 //2: ccc 21 //1: bbbbbbbbbbbb 22 //5: 23 //4: 24 //4: g 25 //4: h 26 //3: fff 27 //2: eeee 28 //1: ddddddd 29 //0: aaaaaaaaaaaaaaaaaaaa 30
Die vorstehenden Kommentarzeilen zeigen einen Aufruf und die zugehörige Ausgabe.
Zwei verschachtelte Leerkommentare sind nur an ihrer Ebenennummer zu erkennen.
aaa... (Ebene 0) zeigt den Text ohne die zuvor darin enthaltenen Kommentare.
Diese Funktion muß Teile einer Zeichenkette suchen, diese miteinander verketten
und dann ausgeben. Das erfordert mehr Speicherplatz auf dem Stack als gewöhnlich
bei rekursiven Funktionen. Oft werden nur etwa 30 Byte pro Aufruf benötigt.
Die Anzahl der ineinander verschachtelten Aufrufe liegt meist bei 3 bis 5.
Bei der Funktion QuickSort wurden maximal etwa 20 solche Aufrufe beobachtet.
Auf dem PC wird einem Prozeß beispielsweise 512 MB Stack-Größe zugeteilt.
Auf Microcontrollern (16 Bit) sind 1 bis 3 KB Stack üblich, bei 32 Bit sind
8 KB bis 32 KB üblich. Es gibt also keine Probleme mit dem Stack-Bedarf.
Fünf rekursive Aufrufe einer Funktion benötigen beispielsweise 200 Byte Stack.
Fünf nichtrekursive verschachtelte Aufrufe verschiedener Funktionen können
insgesamt 1200 Byte Stack benötigen. Hier ist nicht erkennbar, warum speziell
Rekursivität das Stacklimit gefährdet. Dies wird von Programmierern mit
geringerer Erfahrung kolportiert.
Möglicherweise hatten diese mal Erfahrungen mit fehlerhaft programmierter
Rekursivität gemacht.
Problemlösungen, die z.B. 100000 rekursive Aufrufe brauchen, sollten
allerdings regelmäßig nicht rekursiv konzipiert werden.
Eine Misra-Regel verbietet rekursive Funktionen.
Die vorstehende Funktion verstößt gegen viele weitere Misra-Regeln:
Pointer-Arithmetik, Komma-Operator, Verwendung von printf(), logische Operatoren
verwendet wie eine if-Anweisung, Positionierung von Inkrements, fehlende
{ }, fehlende else-Zweige, return nicht nur
am Ende der Funktion, Kommentare per //.
Eine misra-konforme Variante wird hier nicht gezeigt.
Eine solche Entwicklung hätte zu große Übelkeit und weitere geistig-körperliche
Probleme verursacht. Bereits bei zugehörigen Konzeptüberlegungen wird einem
etwas schlecht.
Eine misra-konforme Variante müßte ein Array aus Strukturen oder ein mehrdimensionales
Array enthalten. Ebenso einen Puffer für die Ausgabe.
Die Größe dieser Objekte und die erforderlichen Elementeanzahlen müssen geschätzt werden.
Es müssen mindestens Position, Länge und Ebene von jedem Teil-String durch parsen
festgestellt und gespeichert werden. Zum Schluß muß das Array ausgewertet werden
und die Ausgabe erfolgen.
Als Ausgabefunktion darf keine Standard-Funktion verwendet werden, da von Misra verboten.
Es muß z.B. die POSIX-Funktion write() verwendet werden, falls vorhanden.
Der Inhalt von e muß mit einer selbstentwickelten Funktion itoa() in
einen String umgewandelt werden, da <stdio.h> verboten ist.
Bei der hier gezeigten Funktion muß nur die Größe von buf[] passend
gewählt werden (auch im Zusammenhang mit der maximalen Aufruftiefe).
Die Anzahl der Teilstücke spielt praktisch kaum eine Rolle, nur deren Gesamtlänge.
Eine misra-konforme Funktion wäre beträchtlich größer, komplexer, komplizierter,
unabwägbarer und somit wesentlich unsicherer. Erneut das Gegenteil von dem, was Misra
von misra-konformem Code behauptet.
Nachfolgend eine Funktion, in der das durch eine Misra-Regel verbotene Makro offsetof() verwendet wird:
1 2 // Nicht misra-konformer Code: 3 4 UNS4 GetIdByObjAdr(volatile const void *baseaddr) 5 { 6 CFG_t *sp; 7 UNS n; 8 n= getposlF(CFGdata, offsetof(struct cfgdata_t, name), 9 (UNS4)baseaddr, sizeof(struct cfgdata_t), CFGdata_ne); 10 return (n && (sp=CFGdata+n-1)->name == baseaddr) ? sp->id : 0ul; 11 } 12
Diese Funktion und Code, der diese Funktion verwendet, wurden im Rahmen einer
nachträglichen Erweiterung der Software auf Kundenanforderung entwickelt und implementiert.
Das dauerte 2 Manntage. Hersteller und Kunde waren mit diesem Zeitbedarf
und den Kosten sehr zufrieden.
Ohne Verwendung des Makros offsetof() (Misra verbietet dessen Verwendung)
hätten große Teile der Gesamt-Software neu konzipiert und neu entwickelt
werden müssen. Das hätte etwa 200 Manntage gedauert.
Niemand hätte auch nur ansatzweise diesen Zeitbedarf und die damit verbundenen
Kosten akzeptieren können.
Es darf nicht vergessen werden, daß nach Abschluß der Software-Neuentwicklung
noch über mehrere Jahre hinweg Fehlerbeseitigungen gefolgt wären!
Misra-Konformität hätte hier neue, entscheidende, nützliche Funktionen und einen
Neukunden verhindert.
Die return-Zeile dürfte auch gegen weitere Misra-Regeln verstoßen.
Misra hat es immer gerne, wenn eine Lösung nicht eine Zeile Code braucht, sondern
derer eher fünf, also in Richtung Unübersichtlichkeit und verminderter
Überschaubarkeit - folglich unsichererem Code.
Nachfolgend eine nicht misra-konforme Anwendung von setjmp() longjmp():
1 2 static int SYS_Menu(int a, int b) 3 { 4 if (a<0) return 1; 5 if (b<0) return 1; 6 if (b) return -1; 7 if (!Sys.pwrfail&&!setjmp(MU.jb)) menu_func(); 8 return 0; 9 } 10 11 BYTE GetKeyCode(void) 12 { 13 static DIR BYTE kc, onetime, lang; 14 static DIR UNS4 tfb; 15 BYTE rkc; 16 // ... 17 if (MU.jb_menu==1) { 18 if (rkc) tfb=MU.ul64ms; 19 if (rkc==klESC || MU.ul64ms-tfb>16*60*20 || Auth.Login[LINDEV_KEYS].fallback) 20 Auth.Login[LINDEV_KEYS].fallback=0, 21 CURSOR(0), MU.jb_menu=0, longjmp(MU.jb, 1); 22 } 23 else tfb=MU.ul64ms, Auth.Login[LINDEV_KEYS].fallback=0; 24 return (rkc); 25 } 26
Wenn das Hauptmenü verlassen wird, wird zuvor setjmp() aufgerufen, die den
Wert 0 retourniert, woraufhin menu_func() aufgerufen wird, mit über 100
verschachtelten Menü-Funktionen darin.
In allen diesen Menü-Funktionen wird GetKeyCode() wiederholt aufgerufen.
Wenn (neben anderen Gründen) 20 Minuten lang keine Taste betätigt wird
(rkc==0), wird longjmp() aufgerufen.
Das bewirkt, daß setjmp() nun mit dem Wert 1 retourniert, wodurch die
Grundstellung des Hauptmenüs erreicht wird.
Dies ist ein von Kunden gefordertes Sicherheitsverhalten!
Die Implementation dieses kleinen, simplen und unabhängigen Codes
hatte nur wenige Stunden gedauert, der natürlich auf Anhieb funktionierte.
Die Misra-Regeln hätten diese Sicherheitsfunktionalität praktisch verunmöglicht.
Die Flexibilität dieser Jump-Funktionen ist groß: Es können Zahlenwerte im
int-Bereich für Steuerungszwecke gewählt werden (hier 1).
Es können viele verschiedene Jump-Buffer (MU.jb) verwendet werden.
Der Jump-Buffer kann durch nachfolgende
Aufrufe von setjmp() jeweils überschrieben werden.
Wenn dieses Funktionspaar verboten ist, ist ein Code ohne es, der gleiches bewirkt,
nicht oder nur schwer realisierbar.
Falls er realisierbar ist, wäre er sehr viel größer und entsprechend fehlerträchtig
und damit wesentlich unsicherer. Der neue Code müßte sich im gesamten
Menü-System ausbreiten, viele Modifikationen an bestehendem Code wären notwendig.
Ein Konzept ohne dieses Funktionspaar von vorne an wäre ebenfalls wesentlich
komplexer, unelegant, weniger flexibel und nicht unabhängig.
Der Code enthält mehrere weitere Verstöße gegen Misra-Regeln:
Auffallend sind mehrere return in den Zeilen 4-6. Die sind dort allerdings
sehr logisch und sinnvoll positioniert! Misra-konformer Code wäre hier
affig-verkrampfter Unfug!
Fazit mal wieder: Die Misra-Regeln sind eindeutig massiv schädlich!
Die nachfolgende nicht misra-konforme Funktion korrigiert auf einem Microcontroller selektiv neue und geänderte Konfigurationsparameter (etwa 700) beim Vorliegen einer anderen Firmware-Versionsnummer. Dies geschieht während der StartUp-Phase mit Hilfe von dynamischem Speicher (malloc / free).
1 2 UNS4 adjustconfig(int mode) 3 { 4 // ... 5 } 6 else ctl.ne=CFGdata_ne, ctl.marke=0; 7 ne= ctl.ne>=CFGdata_ne ? ctl.ne : CFGdata_ne; 8 9 np0=np= malloc(ne*sizeof(struct ud_np)); 10 if (!np) { r|=PUUD_MEMNP; goto RET; } 11 memset(np0, 0, ne*sizeof(struct ud_np)); 12 ud0=ud= malloc(ne*sizeof(struct ud_data)*2); // doppelt! 13 if (!ud) { r|=PUUD_MEMUD; goto RET; } 14 memset(ud0, 0, ne*sizeof(struct ud_data)*2); // doppelt! 15 dp0=dp= malloc(CFGdata_ne*sizeof(struct ud_diff)); 16 if (!dp) { r|=PUUD_MEMSP; goto RET; } 17 memset(dp0, 0, CFGdata_ne*sizeof(struct ud_diff)); 18 if (PlenMem(96*40)) { r|=PUUD_MEMPAR; goto RET; } 19 20 if (r&PUUD_CRC) goto NOLIST; 21 Copy_sp(dp0, CFGdata); // Auszug aus CONST Konfiguration 22 qsort(dp0, CFGdata_ne-1, sizeof(struct ud_diff), cmp_dp); 23 if (UD.err) goto RET; 24 25 if (ctl.marke==0xDEFA) { // Es gibt also schon eine Liste 26 // ... 118 Zeilen 27 ctl.addel = add; 28 ctl.lenchg = len; 29 ctl.typchg = typ; 30 ctl.crc = CRC16(CRC0, ud0, ctl.ne*sizeof(struct ud_data)); 31 FL_write(FL_UD_DATA, ud0, ctl.ne*sizeof(struct ud_data)); 32 FL_rewrite(FL_UD_DATA, (ctl.ne*sizeof(struct ud_data)+_264-1)/_264); 33 ctl.crc2= CRC16(CRC0, &ctl, sizeof(ctl)); //neu 34 FL_write(FL_UD_CNTL, &ctl, sizeof(ctl)); 35 FL_rewrite(FL_UD_CNTL, (sizeof(ctl)+_264-1)/_264); 36 RET:; 37 free(dp0); 38 free(ud0); 39 free(np0); 40 free(m_buf); m_buf=0; m_size=0; 41 return r|UD.err; 42 } 43
Auf Microcontrollern mit 4 KB bis 20 KB RAM war eine solche Funktion
nicht möglich. Es mußte daher leider bei jeder Änderung der Firmware
die Konfiguration pauschal komplett auf Default-Werte gesetzt (überschrieben)
werden.
Ab etwa 150 KB RAM ist es möglich, die benötigte Speichermenge vorübergehend
per dynamischer Speicherallokation zu erlangen, um nur neue Parameter auf
Default-Werte setzen zu müssen und irgendwie geänderte Parameter selektiv
zu korrigieren.
Dies ist ein absolut wichtiges, hochrangiges Ausstattungsmerkmal!
Auch diese für Kunden außerordentlich wichtige Funktion wird durch
das Misra-Regelwerk verunmöglicht!
Dynamischer Speicher ist nämlich schlicht verboten.
Aus Sicherheitsgründen wird die Funktion sofort verlassen, sobald ein
Fehler auftritt (goto / return), mit Diagnose und
Freigabe von dynamischem Speicher.
Dies wird ebenfalls durch Misra-Regeln praktisch verhindert: nämlich
goto und mehr als ein return sind verboten.
Eine 30-fache if-else-Verschachtelung wäre blanke Unvernunft!
Konstruktionen mit Hilfsvariablen wären normalerweise ein Grund für die
Kündigung des hierbei aktiven Programmierers.
Aber im Misra-Wahn spielt das keine Rolle: es muß grausam verunstaltet
und affig-verdreht programmiert werden...
Nachfolgend ein switch-case mit goto und anschließend misra-konform ohne goto:
1 2 /* Nicht misra-konformer Code: */ 3 4 case 5: // Switch OFF -> Flatpacks 5 if (++z,u=0, PAC.K[k].cmd&PAC_AUS&&PAC.anz) { 6 PACON:; 7 if (CanE_CheckChanFree()>0) { 8 id= evMAKEGENMSG(buf, 5,0,2, 11,0,19,38,1,0); 9 buf[3]=1; buf[4]=255; buf[5]=buf[6]=off=u?0:1; buf[7]=0; 10 CanE_Transmit(id, 7, buf); 11 break; 12 } 13 } 14 else if (off) { u=1; goto PACON; } 15 case 6: // Switch OFF -> DCDC 16 17 /* Misra-konformer Code: */ 18 19 while ( 1 ) { 20 fall_trough=0; 21 /* ... */ 22 case 5: /* Switch OFF -> Flatpacks */ 23 ++z; 24 if ((PAC.K[k].cmd&PAC_AUS)&&PAC.anz) { 25 if (CanE_CheckChanFree()>0) { 26 id= evMAKEGENMSG(buf, 5,0,2, 11,0,19,38,1,0); 27 buf[3]=1; buf[4]=255; buf[5]=buf[6]=off=1; buf[7]=0; 28 CanE_Transmit(id, 7, buf); 29 } 30 else { fall_trough=1; } 31 } 32 else { 33 if (off) { 34 if (CanE_CheckChanFree()>0) { 35 id= evMAKEGENMSG(buf, 5,0,2, 11,0,19,38,1,0); 36 buf[3]=1; buf[4]=255; buf[5]=buf[6]=off=0; buf[7]=0; 37 CanE_Transmit(id, 7, buf); 38 } 39 else { fall_trough=1; } 40 } 41 else { fall_trough=1; } 42 } 43 break; 44 case 6: /* Switch OFF -> DCDC */ 45 /* ... */ 46 if (!fall_trough) break; 47 } 48
Der misra-konforme Code wurde durch die Misra-Regeln gezwungenermaßen
zu einem idiotischen, mehr als doppelt so großen Code.
Desweiteren ist die Verschachtelung tiefer und der Kontrollfluß unübersichtlich.
Weiterhin muß eine Hilfsvariable für den Kontrollfluß verwendet werden.
Das ist eine Kardinal-Sünde! Ebenso allgemeine Programmier-Sünden sind hier
der doppelte Code und mehrfaches notwendiges Setzen der Hilfsvariable.
Es ist zu beachten, daß die Hilfsvariable jeweils oft mehrfach von case 1:
bis case 38: gesetzt werden muß.
Alle vorstehenden Eigenschaften erhöhen die Fehlerwahrscheinlichkeit!
Misra-Konformität macht Code nahezu grundsätzlich unsicherer!
Der nicht misra-konforme Code ist wesentlich kleiner und übersichtlicher.
Im switch wird solange durchgefallen, bis erfolgreich eine
CAN-Message abgesetzt werden konnte, erkennbar am einzelnen positiven
break in Zeile 11. Das ist unbedingt notwendig, da die
Funktion, die den switch enthält, nicht sinnlos (ohne CAN-Transmit)
aufgerufen werden darf.