Inhaltsverzeichnis
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> ... unsigned char sreg = SREG; // Das sollte UNBEDINGT eine lokale Variable sein, // sonst müßte man explizit auf den STACK pushen. // Sonst wäre nämlich diese Programmstelle nicht reentrant, was // recht tückisch sein kann. cli(); if (0 == i) { i = 1; SREG = sreg; } else { SREG = sreg; return; } ...
Oder je nach Programmstruktur sichert man den Wert, z.B. in eine lokale Variable oder deaktiviert nur selektiv die kritischen Interrupts.