Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Rasenmaehroboter fuer schwierige und grosse Gaerten im Test

(Ausgelagert auf avr-gcc, um den Artikel zu kürzen, zudem ist das Thema AVR-unabhängig)
 
K
Zeile 1: Zeile 1:
==Tippfehler==
+
=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.
 
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 <tt>;</tt> zu viel===
+
==Ein <tt>;</tt> zu viel==
 
Ein reflexartig eingetippter oder nach Ändeungen stehen gebliebener <tt>;</tt> hat schon so manches  
 
Ein reflexartig eingetippter oder nach Ändeungen stehen gebliebener <tt>;</tt> hat schon so manches  
 
Programm ausgeknockt:
 
Programm ausgeknockt:
Zeile 12: Zeile 12:
 
Wenn <tt>a == 0</tt> ist, dann wird <tt>;</tt> ausgeführt (also im Endeffekt garnichts). Danach kommt der Block, der im <tt>if</tt> stehen sollte. Der wird immer ausgeführt, denn er gehört nicht mehr zum <tt>if</tt>.
 
Wenn <tt>a == 0</tt> ist, dann wird <tt>;</tt> ausgeführt (also im Endeffekt garnichts). Danach kommt der Block, der im <tt>if</tt> stehen sollte. Der wird immer ausgeführt, denn er gehört nicht mehr zum <tt>if</tt>.
  
===Zuweisung statt Vergleich===
+
==Zuweisung statt Vergleich==
 
  if (a = 0)
 
  if (a = 0)
 
  {
 
  {
Zeile 24: Zeile 24:
  
  
===Signal/Interrupt-Name vertippt oder Leerzeichen zu viel (avr-gcc)===
+
==Signal/Interrupt-Name vertippt (avr-gcc)==
  
 
  SIGNAL (SIG_OVEFRLOW0)
 
  SIGNAL (SIG_OVEFRLOW0)
Zeile 30: Zeile 30:
 
     /* mach was */
 
     /* 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.
+
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==
+
=Mangelnde C-Kenntnis=
  
===Warnung ignoriert===
+
==Warnung ignoriert==
  
 
::"''Wieso soll das Probleme machen? Das ist doch nur eine Warnung!''"
 
::"''Wieso soll das Probleme machen? Das ist doch nur eine Warnung!''"
Zeile 59: Zeile 59:
 
</pre>
 
</pre>
  
===Array-Index===
+
==Array-Index==
  
 
Informatiker am Bahnhof:
 
Informatiker am Bahnhof:
Zeile 70: Zeile 70:
 
plötzlich seltsame Werte enthalten.
 
plötzlich seltsame Werte enthalten.
  
===Bitweise vs. Logische Operatoren===
+
==Bitweise vs. Logische Operatoren==
  
 
Die Operatoren AND, OR und NOT gibt es in C in zwei Ausprägungen
 
Die Operatoren AND, OR und NOT gibt es in C in zwei Ausprägungen
Zeile 80: Zeile 80:
 
Ein Verwechseln bzw. unkorrektes Einsetzen der Operatoren gibt also ein falsches Programm.
 
Ein Verwechseln bzw. unkorrektes Einsetzen der Operatoren gibt also ein falsches Programm.
  
===TRUE ist nicht 1===
+
==TRUE ist nicht 1==
  
 
Da C die Begriffe TRUE und FALSE eigentlich nicht kennt, wurde schon öfter beobachtet, daß folgendes definiert wurde:  
 
Da C die Begriffe TRUE und FALSE eigentlich nicht kennt, wurde schon öfter beobachtet, daß folgendes definiert wurde:  
Zeile 105: Zeile 105:
 
  if (a == TRUE) // oder die 'klassische' Variante: if (a)
 
  if (a == TRUE) // oder die 'klassische' Variante: if (a)
  
===Ein , anstatt . in Konstante===
+
==Ein , anstatt . in Konstante==
  
 
In C sowie im angloamerikanischen Sprachraum werden Dezimalbrüche mit einem <tt>.</tt> (Punkt) geschrieben und nicht wie im Deutschen mit einem <tt>,</tt> (Komma):  
 
In C sowie im angloamerikanischen Sprachraum werden Dezimalbrüche mit einem <tt>.</tt> (Punkt) geschrieben und nicht wie im Deutschen mit einem <tt>,</tt> (Komma):  
Zeile 122: Zeile 122:
 
Das falsche Komma hat auch schon so manchen ebay-Freak aufs Kreuz gelegt...
 
Das falsche Komma hat auch schon so manchen ebay-Freak aufs Kreuz gelegt...
  
===Führende 0 in Konstanten===
+
==Führende 0 in Konstanten==
  
 
In C kennzeichnet eine führende 0 bei einer Zahlenkonstante,  
 
In C kennzeichnet eine führende 0 bei einer Zahlenkonstante,  
Zeile 128: Zeile 128:
 
  if (a == 030) // ist a gleich 24?
 
  if (a == 030) // ist a gleich 24?
  
==Nicht-atomarer Code==
+
=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.  
+
Verwendet man in einem Programm [[IRQ|IRQs]] ([[Interrupt|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 Beispiel, das zeigt, was passieren kann, findet sich im Abschnitt "[[avr-gcc#Zugriff auf einzelne Bits|Zugriff auf einzelne Bits]]". Ein weiteres Beispiel ist das Lesen, Schreiben oder Testen einer mehrbytigen Variable:
+
Ein ausführlicheres Beispiel, das zeigt, was passieren kann, findet sich im Artikel [[avr-gcc]] im Abschnitt "[[avr-gcc#Zugriff auf einzelne Bits|Zugriff auf einzelne Bits]]". Ein weiteres Beispiel ist das Lesen, Schreiben oder Testen einer mehrbytigen Variable:
 
<pre>
 
<pre>
 
int volatile i;
 
int volatile i;
Zeile 180: Zeile 180:
 
Oder je nach Programmstruktur sichert man den Wert, z.B. in eine lokale Variable oder deaktiviert nur selektiv die kritischen Interrupts.
 
Oder je nach Programmstruktur sichert man den Wert, z.B. in eine lokale Variable oder deaktiviert nur selektiv die kritischen Interrupts.
  
== Weitere Fallstricke ==
+
= Weitere Fallstricke =
 
* [[Warteschleife]]
 
* [[Warteschleife]]
  
== Siehe auch ==
+
= Siehe auch =
 
* [[C-Tutorial]]
 
* [[C-Tutorial]]
 
* [[avr-gcc]]
 
* [[avr-gcc]]

Version vom 14. Februar 2006, 11:44 Uhr

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 Ändeungen 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.

Weitere Fallstricke

Siehe auch


LiFePO4 Speicher Test