Copyright 2014 © Helmut O.B. Schellong

Bei den nachfolgenden Beispielen geht es darum, möglichst vollständig einen nicht misra-konformen Originalcode misra-konform umzuformen.
Die einzelnen Anweisungen und deren Abfolge müssen dabei möglichst gleich bleiben.

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.