Aus RN-Wissen.de
Version vom 21. November 2006, 18:26 Uhr von SprinterSB_alt (Diskussion | Beiträge) (Nicht-atomarer Code)

Wechseln zu: Navigation, Suche
Rasenmaehroboter fuer schwierige und grosse Gaerten im Test

Tippfehler

Tippfehler können immer passieren. Besonders fies ist es, wenn der Tippfehler nicht zu einer Warnung oder zu einer Fehlermeldung führt, weil der entstandene Code korrekter C-Code ist.

Ein ; zu viel

Ein reflexartig eingetippter oder nach Änderungen stehen gebliebener ; hat schon so manches Programm ausgeknockt:

if (a == 0);
{
   /* mach was */
}

Wenn a == 0 ist, dann wird ; ausgeführt (also im Endeffekt garnichts). Danach kommt der Block, der im if stehen sollte. Der wird immer ausgeführt, denn er gehört nicht mehr zum if.

Zuweisung statt Vergleich

if (a = 0)
{
   /* mach was */
}

Zuerst wird a = 0 gesetzt und dann überprüft, ob die if-Bedingung erfullt ist. Der Wert ist aber immer 0, was nicht erfüllt bedeutet. Der nachfolgende Block wird nie betreten.

Abhilfe schafft, indem man sich angewöhnt zu schreiben

if (0 == a)

Wenn man dann eine Zuweisung eintippt, gibt's einen Compiler-Fehler.


Signal/Interrupt-Name vertippt (avr-gcc)

SIGNAL (SIG_OVEFRLOW0)
{
   /* mach was */
}

Nicht alle Compiler-Versionen meckern da. Der ISR-Code wird nicht in die Interrupt-Tabelle eingetragen. Kommt es zum Interrupt, dann landet man in RESET.


Mangelnde C-Kenntnis

Warnung ignoriert

"Wieso soll das Probleme machen? Das ist doch nur eine Warnung!"

Warnungen zur Compile-Zeit werden gerne zu Fehlern zur Laufzeit. Letztere sind deutlich schwerer zu finden, als angewarnten Code zu korrigieren.

void foo (long*);

void bar (int *p)
{
    foo (p+1); 
    // Was soll das sein?!
    // ((long*) p) + 1       oder  
    // (long*) (p + 1)
}
> gcc -c -o prog.o prog.c

prog.c: In function `bar':
prog.c:5: warning: passing arg 1 of `foo' from incompatible pointer type

Array-Index

Informatiker am Bahnhof:

"0, 1, 2, ... Wo ist mein dritter Koffer?!"

Gleiches gilt für Arrays:

#define NUM 10;
int a[NUM]; // für die N Werte a[0] ... a[N-1]

Ein Zugriff auf a[N] greift igrdendwo hin. Wahlweise liest man Schrott oder überschreibt andere Daten, die in der Nähe liegen, und plötzlich seltsame Werte enthalten.

Bitweise vs. Logische Operatoren

Die Operatoren AND, OR und NOT gibt es in C in zwei Ausprägungen

bitweise
Die Operatoren ^, |, &, ~, |=, ^=, &=, |= operieren bitweise. Der entsprechende Operator wird also auf alle Bits des Wertes parallel angewandt und das Ergebnis für ein Bit ist unabhängig vom Inhalt der anderen Bits.
logisch
Die Operatoren ||, &&, !, &&=, ||= operieren auf dem ganzen (int) Wert und berücksichtigen nur, ob der Wert 0 (false) oder ungleich 0 (true) ist.

Dementsprechend liefert && i.d.R. ein anderes Ergebnis als &:

if (a && b) // erfüllt, wenn a!=0 und b!=0 
if (a & b)  // erfüllt, wenn in a und b an der gleichen Stelle ein Bit gesetzt ist

Ein Verwechseln bzw. unkorrektes Einsetzen der Operatoren gibt also ein falsches Programm.

TRUE ist nicht 1

Da C die Begriffe TRUE und FALSE eigentlich nicht kennt, wurde schon öfter beobachtet, daß folgendes definiert wurde:

#define TRUE 1
#define FALSE 0

solange man damit nur Schalter setzt und abfragt, ist dagegen nichts zu sagen.

a = TRUE; b = FALSE;
if ( (a == TRUE) && (b == FALSE)) {.."this is true"..} 

wenn man aber dann schreibt

if ( (a & b) == TRUE)  {.."this is true"..}

kann man einen herbe Enttäuschung erleben. Man müßte für ein richtiges Ergebnis schreiben

if ( (a & b) != FALSE)  {.."this is true"..}

Damit ist aber eine gute Lesbarkeit endgültig dahin.

Besser sind Definition wie

#define FALSE (0!=0)
#define TRUE  (0==0)

denn damit gilt einerseits, daß für alle boolschen Werte einschliesslich dieser Konstanten der Zusammenhang x = !!x besteht. Mit der allerersten Definition hat man das unerwünschte Ergebnis TRUE != !FALSE. Andererseits kann man direkt gegen diese Konstanten vergleichen:

a = (x < y);
...
if (a == TRUE) // oder die 'klassische' Variante: if (a)

Ein , anstatt . in Konstante

In C sowie im angloamerikanischen Sprachraum werden Dezimalbrüche mit einem . (Punkt) geschrieben und nicht wie im Deutschen mit einem , (Komma):

float pi;
pi = 3,14;         // *AUTSCH* soll wohl heissen 3.14

Die zweite Zeile besteht aus zwei durch ein Komma getrennten Anweisungen, so daß das ganze etwa gleichbedeutend ist mit

float pi;
pi = 3;
14;

Jedenfalls ist es korrekter C-Code! Die 14; ist ein Ausdruck, der nicht weiter gebraucht wird und daher wegfällt, da er im Gegensatz zu einer void-Funktion keine Wirkung hat. Man rechnet also mit dem Wert 3 für [math]\pi[/math].

Das falsche Komma hat auch schon so manchen ebay-Freak aufs Kreuz gelegt...

Führende 0 in Konstanten

In C kennzeichnet eine führende 0 bei einer Zahlenkonstante, daß die Zahl oktal dargestellt ist. Somit ist 010 nicht gleich 10.

if (a == 030) // ist a gleich 24?

Nicht-atomarer Code

Verwendet man in einem Programm IRQs (Interrupts) und ändert in der ISR (Service Routine) ein Datum (Variable, SFR, ...), dann wird diese Änderung möglicherweise überschrieben, wenn die IRQ zu einem ungünstigen Zeitpunkt auftritt.

Ein ausführlicheres Beispiel, das zeigt, was passieren kann, findet sich im Artikel avr-gcc im Abschnitt "Zugriff auf einzelne Bits". Ein weiteres Beispiel ist das Lesen, Schreiben oder Testen einer mehrbytigen Variable:

int volatile i;
   ...
   if (0 == i)

Weil i länger als 1 Byte ist, kann es je nach Architektur nicht in einem Befehl gelesen werden; daher kann während des Lesens eine IRQ auftreten, in deren ISR der Wert von i möglicherweise verändert wird. Der obige Code könnte etwa so assembliert werden (hier mit avr-gcc):

   ; i wird vom SRAM in das Registerpaar r24:r25 geladen
   ; low-Teil laden
   lds r24,i
   ; high-Teil laden
   ; wenn hier eine IRQ zuschlägt, in der i verändert wird, ist der Registerinhalt korrupt
   lds r25,(i)+1
   or r24,r25
   brne ...

Natürlich können auch komplexere Datenstrukturen von diesem Phänomen betroffen sein. Besonders unangenehm an dieser Klasse von Fehlern ist, daß sie nur sporadisch auftauchen und man sie daher selbst mit einem guten Debugger sehr schlecht orten kann, da die zugehörige Codestelle fast immer korrekt abgearbeitet wird.

Eine Möglichkeit dem zu begegnen ist, den ganzen betroffenen Block ununterbrechbar (atomar) zu machen. Wieder ein Beispiel für avr-gcc:

#include <avr/io.h>
#include <avr/interrupt.h>
...
   // ein lokale(!) Variable, die das Status-Register und 
   // insbesondere das im folgenden geänderte I-Flag merkt 
   // Falls man an der Codestelle immer weiß, daß IRQs 
   // aktiviert sind, kann man die zu atomisierende Sequenz 
   // auch in cli()...sei() einschachteln 
   // Falls IRQs global deaktiviert sind brauch man natürlich 
   // keine besonderen Vorkehrungen zu treffen, da dann aller Code 
   // atomar ist. 
   unsigned char sreg = SREG;

   // Interrupts global deaktivieren (I-Flag = 0) 
   cli();

   if (0 == i)
   {
       i = 1;
       // IRQs sobald als möglich wieder zulassen 
       // (I-Flag wieder herstellen) 
       SREG = sreg;
   }
   else
   {
       // IRQs so bald als möglich wieder zulassen 
       // (I-Flag wieder herstellen) 
       SREG = sreg;
       return;
   }
   ...

Oder je nach Programmstruktur sichert man den Wert, z.B. in eine lokale Variable oder deaktiviert nur selektiv die kritischen Interrupts, also solche, die Einfluss auf die kritischen Daten nehmen.

Weitere Fallstricke

Siehe auch


LiFePO4 Speicher Test