Debugger
Der Debugger ist ein Programm zur schrittweise Ausführung von übersetztem Quellcode zur Aufdeckung von logischen Fehlern (Bugs).
Es gibt zwei Arten von Programmierfehlern: syntaktische und semantische.
Programmierfehler, welche die Syntax eines Programms betreffen, werden durch den Compiler erkannt (compile time error).
Mit dem Debugger erkennt man logische (semantische) Fehler, die erst bei der Ausführung eines Programms auftreten (run time error).
Die wichtigste Aufgabe bei der logischen Fehlerbehebung ist zuerst die Lokalisation des Fehlers im Quellcode. Dazu muss der Fehler reproduzierbar sein. Ist der Fehler lokalisiert, so ist die Behebung der Ursache meist recht offensichtlich.
Zur Lokalisation eines Fehlers, führt man das betreffende Programm Schritt für Schritt aus bis eine Diskrepanz zwischen dem, was das Programm tun sollte und dem was es tatsächlich tut, auftaucht. Der letzte ausgeführte Schritt (in der zuletzt ausgeführten dazugehörigen Quellcode-Zeile) muss dann für den Fehler verantworlich sein.
Ein frei erhältlicher Debugger ist der gdb (siehe auch unter GCC Tutorial).
Ein Beispiel zu einem Syntaxfehler:
{
int a=0
scanf("%d", &a);
if (a=1) printf("duh\n");
}
Obiges Programm ergibt zwei Compiler-Meldungen, d.h. eine echte Compiler-Fehlermeldung und eine Compiler-Warnung. Die erste wegen einem vergessenen Semikolon, die zweite weil = mit == verwechselt wurde. Fehler, die von einem einzigen falschen Zeichen herrühren, nennt man auch Typo.
Bei letzterem Typo ist der Ausdruck zwar syntaktisch nicht falsch, aber von der Semantik her macht der Ausdruck keinen Sinn. Solche trivialen logischen Fehler kann der Compiler erkennen, er kann aber nur davor warnen. Compiler-Warnungen sind daher prinzipiell immer ernst zu nehmen! Ein gutes C Programm verursacht keine Warnungen.
Ein Beispiel zu einem Logikfehler:
{
int i;
int a=0;
/* read non-zero integer, try 10 times at most */
i=0;
do {
scanf("%d", &a);
} while (a==0 || i++<10);
}
- Symptom: Schleife wird nach 10 Versuchen nicht automatisch abgebrochen.
- Debugger: Schritt für Schritt bis zur Abbruchbedingung herantasten
- Analyse: Abbruch-Bedingung bzw. entsprechende Variablenwerte überprüfen.
- Problem: || anstelle von &&, Post-Inkrement anstelle von Pre-Inkrement.
Man unterscheidet zwischen Ursache und Wirkung eines Fehlers. Mit dem Debugger lokalisiert man das Auftreten eines Programmierfehlers aber nicht notwendigerweise dessen Ursache. Der kausale Zusammenhang muss nicht immer direkt vorliegen, denn häufig ergeben sich aus einem Programmierfehler zuerst Folgefehler, die noch lange nach dem ursächlichen Fehler ein Problem hervorrufen können.
Ein Beispiel zu einem Folgefehler:
{
*b+=*a;
}
int main()
{
static const int n=7;
int a,b;
for (int i=1; i<n; i++)
if (i&1) fibonacci(&a, &b);
else fibonacci(&b, a);
}
- Symptom: Speicherverletzung
- Debugger: Schritt für Schritt bis Speicherverletzung auftritt.
- Wirkung: Debugger meldet Speicherverletzung in Funktion fibonacci.
- Analyse: Dereferenzierung eines ungültigen Pointers
- Ursache: Vergessener Adress-Operator im else Zweig von main.