[X] Debuggen mit Visual C++ 6



  • Worum geht's hier?
    Über Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert.
    Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.

    Ich möchte in diesem Artikel alles zusammentragen, was mir bisher zum Debuggen bekannt ist.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen, und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden.
    Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CAd3App::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Bei einigen steht da etwas wie

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zu Prüfung, ob für die Programmausführung nötige Werte eingehalten werden.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT ist ein Befehl, der nur in der Debugversion funktioniert.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und Sie können das auch.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Das glaube ich eher nicht. Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.
    Eine Liste mit Fehlern in der MFC gibt es hier.

    Sollte es mal so scheinen, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.

    Sollte das alles nicht helfen, haben Sie irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue der Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?

    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.

    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab.
    Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie dort einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten



  • Danke Marcus! 🙂

    Ich habe das mal in meine Worte umgebaut, da deine Formulierung wohl sehr herausgestochen hätte. Solche Konstrukte kriege ich nicht hin. 😮

    PS: Ich habe mal eine Bitte:
    Kann jemand von Euch mal die aktuelle Fassung in ein PDF drucken und mir bis 12:00 mailen?
    FireFox und IE stürzen bei jedem Druckversuch ab. 🙄
    Und ich kann auf dem Papier irgendwie besser Korrekturlesen. Da finde ich Sachen, die ich auf dem Monitor nicht mehr sehe. Das würde ich dann gerne in meiner Pause machen.



  • Danke, ich hab jetzt ein PDF. 🙂



  • estartu_de schrieb:

    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Es klingt zwar überheblich, aber in der MFC sind keine Fehler. Davon können Sie einfach ausgehen und müssen dort nicht suchen.

    Sollte es mal so scheinen, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.

    Sollte das alles nicht helfen, haben Sie irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Ich fand die erste (schärfere) Formulierung irgendwie schöner. 😉
    Die war so schön aggressiv... 🙄

    Kannst du die neue und die alte nicht irgendwie verbinden???? 🙂

    Mr. B



  • Worum geht's hier?
    Über Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert.
    Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.

    Ich möchte in diesem Artikel alles zusammentragen, was mir bisher zum Debuggen bekannt ist.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen, und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden.
    Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CAd3App::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Bei einigen steht da etwas wie

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zu Prüfung, ob für die Programmausführung nötige Werte eingehalten werden.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT ist ein Befehl, der nur in der Debugversion funktioniert.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und Sie können das auch.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue der Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?

    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.

    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab.
    Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie dort einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten



  • Mr. B schrieb:

    Ich fand die erste (schärfere) Formulierung irgendwie schöner. 😉
    Die war so schön aggressiv... 🙄

    Kannst du die neue und die alte nicht irgendwie verbinden???? 🙂

    Mr. B

    Ich habs mal versucht, wie gefällt es dir jetzt?

    Ich finde den Satz auch so klasse, weil ich den damals von dem Kollegen zu hören bekommen habe, der mir so viel beigebracht hat.
    Das holte mich dann ziemlich schnell auf den Boden der Tatsachen zurück. 😃
    ...damals half ein RebuildAll. 🙄



  • gut 👍



  • Worum geht's hier?
    Über Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert. Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen, und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.

    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden. Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CMyApp::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Einige sehen etwa so aus:

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zu Prüfung, ob für die Programmausführung nötige Werte eingehalten werden.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT ist ein Befehl, der nur in der Debugversion funktioniert.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat. Eine häufige Ursache ist, dass das Fenster noch nicht oder nicht mehr existiert.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay***,***

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und Sie können das auch.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    Nun können Sie sich so im Code bewegen, als wären Sie auf einen Breakpoint aufgelaufen.
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue dieser Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?

    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.
    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab. Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten



  • So, ich habe die geänderten Stellen nochmal gekennzeichnet.
    Das ist das Ergebnis, was beim Papierlesen herausgekommen ist. Ich bin damit eigentlich zufrieden.

    Wenn ich jetzt noch ein technisches Okay bekomme, gebe ich es wieder Mr.B zum Korrigieren frei und dann wäre ich fertig. 😃



  • Wie versprochen, habe ich nochmal druebergeschaut und mir ist die ein oder andere
    Kleinigkeit noch aufgefallen, hier das Resultat:

    estartu_de schrieb:

    Worum geht's hier?
    Über Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert. Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden. Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CMyApp::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Einige sehen etwa so aus:

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zur Prüfung, ob für die Programmausführung nötige Werte eingehalten werden.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT ist ein Befehl, der nur in der Debugversion funktioniert.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat. Eine häufige Ursache ist, dass das Fenster noch nicht oder nicht mehr existiert.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay,

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und Sie können das auch.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    Nun können Sie sich so im Code bewegen, als wären Sie auf einen Breakpoint aufgelaufen.
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue dieser Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?

    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.
    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab. Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten

    Was ich noch fragen wollte: Ist ASSERT ein Befehl oder ist ASSERT ein Makro? Wenn letzteres, sollte man das
    im Text noch aendern, imho.

    mfg
    v R



  • Worum geht's hier?
    Über Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert. Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden. Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CMyApp::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Einige sehen etwa so aus:

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zur Prüfung, ob für die Programmausführung nötige Werte eingehalten werden. ASSERT ist "nur" ein Makro, wie auch TRACE und VERIFY. Sie erkennen das daran, dass es komplett groß geschrieben wird.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT wird in der Release-Version durch "nichts" ersetzt.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat. Eine häufige Ursache ist, dass das Fenster noch nicht oder nicht mehr existiert.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay,

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und Sie können das auch.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    Nun können Sie sich so im Code bewegen, als wären Sie auf einen Breakpoint aufgelaufen.
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue dieser Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?

    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.
    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab. Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten



  • So, das mit dem TRACE habe ich nachgebessert. 🙂

    Sonst sind keine Fragen offen geblieben? Wow, dann würde ich sagen: Thematisch fertig. 😃
    Sollte das jetzt noch jemand lesen, der ein VC6 hat? Oder nur noch Rechtschreibendprüfung und raus?



  • Morgen,

    imho waere es schon nicht schlecht, wenn jemand, der den VC6 besitzt, zumindest einmal die Punkte kurz
    durchgeht und somit nachvollzieht.

    mfg
    v R



  • Also ich hab zwar noch nicht alles gelesen aber den Teil den ich gelesen habe möchte ich auch gleich schnell "korrigieren". Da es meine erste Korrektur ist und der Thread "wie korrigiere ich" nicht grad ne schöne Beschreibung ist wie man korrigiert seit bitte gnädig mit mir.
    Als erstes mal sollte ich ja die Verständlichkeit prüfen, da ich ja noch nicht so der Profi bin. Und ich würde sagen im großen und ganzen bin ich richtig gut damit zurecht gekommen, aber ein paar Kleinigkeiten hab ich mal noch im Text markiert. Wenn ich was als falschen Text markiere muss das nicht unbedingt heißen dass er falsch ist, sondern eher dass er mir komisch vor kommt.
    Ich hab einfach mal alles rein geschrieben was mir so in den Sinn kam als ich das gelesen hab. (Sind alles nur Tips wenn du anderer Meinung bist ignoriere sie einfach)
    Also:

    estartu_de schrieb:

    Worum geht's hier?
    Über Programmieren ~klein oder "Programmieren"~ im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers hat**~darstellt fände ich schöner~**.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert. Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden. Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen"~wie findet man das~ noch um viele nützliche Schaltflächen erweitern können (und sollten). ~und wie?~
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 ~stimmt doch gar nicht ich hab die Möglichkeit mit strg F10~ zu starten, das bietet sich bei komplexen Programmen aber nicht an. ~zweifle ich an, z.B.Wenn ich grad an ner Funktion schreib und wissen will ob er in ne Bedingung rein läuft, dann ist doch egal wie komplex es ist mach ich einfach schnell strg + F10~

    Sie können einzelne Breakpoints auch nur unter einer Bedingung aktivieren.~Missverständlich (um zu verdeutlichen: Also ich kann Breakpoints immer und überall aktivieren)~
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem o.g. Dialog ~würde Breakpoint-Verwalungs-Dialog o.ä. verwenden wusste im ersten Moment nicht welchen Dialog du meinst~ festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    ~ich benutze soo oft strg+F10~
    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern ~musste ich zwei mal lesen~ aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CMyApp::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü ~weiß jeder was das ist und wie er es findet?~ aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Einige sehen etwa so aus:

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welcher Befehl gerade an der Reihe ist.~eher welche Zeile. Gedanke:mehrere Befehle in einer Zeile~ Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten**~nicht auseinander?~** Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zur Prüfung, ob für die Programmausführung nötige Werte eingehalten werden. ASSERT ist "nur" ein Makro, wie auch TRACE und VERIFY. Sie erkennen das daran, dass es komplett groß geschrieben wird.
    Ein ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist der**~nicht das?~** ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs melden,~missverständlich (verdeutlichung: Ich lass mich melden? Hä~ wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT wird in der Release-Version durch "nichts" ersetzt.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat. Eine häufige Ursache ist, dass das Fenster noch nicht oder nicht mehr existiert.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay,

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und [kor]Sie können das auch.[kor]~und wie? IMHO besser und Sie können das wie folgt auch o.ä.~
    __________________________________________________________________________________________________



  • Kurze Zwischenfrage: Was macht Strg+F10? 😕
    Und, wie man Symbolleisten einrichtet, sollte man wissen - ich erkläre ja auch nicht die Handhabung der Maus. 😉
    Auch was ein Kontextmenü ist sollte man doch wissen, oder? 😕

    Aber wo "Anpassen" ist, erkläre ich wirklich noch, okay.

    ...mehr dann Morgen. 🙂

    PS: Ich warte aber noch den Rest deiner Korrektur ab.



  • also ich les jetzt noch den Rest aber noch kurz zuvor strg + F10 ist run to Cursor zu dieser stelle; im Prinzip F9 F5 aber wenn du halt so beim Step by Step Debuggen bist, und du kommst in ne Schleife dann kannste mit Cursor down schnell drei Zeilen runter und dann kannste strg +F10 und bist aus der Schleife draußen.

    Zum Rest, jo haste schon recht, ich hätte es ja auch gewusst nur wusste nicht ob das jeder weiß. 😉

    also ich fang jetzt mal an lesen



  • estartu_de schrieb:

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    also ich fände jetzt noch interessant zu wissen wann wende ich am besten Makros an und wie? Nur beim Tracen oder wann noch??
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    hier würde ich noch schreiben dass man über die Variable gehen kann und mit der rechten Maustaste Quick watch machen kann, damit man die Variablen nicht immer eintippen muss
    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    Nun können Sie sich so im Code bewegen, als wären Sie auf einen Breakpoint aufgelaufen.
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue dieser Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?
    hier fehlt IMHO der Link zu dem Artikel aufCodeproject!
    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.
    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab. Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug. oder mit shift F5
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten

    So fertig. Auch wenn ich viel genörgelt hab darfst du das nicht so ernst nehmen. Ich finde den Artikel echt Klasse und würde mich freuen noch viele solcher oder ähnlicher Artikel von dir lesen zu dürfen.
    😃 👍



  • Worum geht's hier?
    Über das Programmieren im Sinne von "Quellcode erstellen" gibt es unzählige Bücher.
    Über das Finden und Beheben von Fehlern leider nicht, obwohl es genauso wichtig ist und auch einen großen Anteil am Arbeitsleben eines Softwareentwicklers darstellt.
    Denn auch nach mehreren Jahren als Softwareentwickler ist das Debuggen von Anwendungen eine tägliche Aufgabe, da man wohl nie auf Anhieb fehlerfreien Code produziert. Im Laufe der Jahre eignet man sich als debuggender Entwickler viele Tricks und Kniffe an, die auch Anfänger zu Beginn ihrer "Laufbahn" schon gut gebrauchen könnten.
    In diesem Artikel erfahren Sie von mir die ersten Grundlagen und ich werde Ihnen den oftmals steinigen Weg des Debuggens zeigen.
    Angefangen bei den Grundlagen bis hin zu häufigen Fehlern, die sich im Debugger erkennen lassen und auch Techniken, mit denen Sie sich die Fehlersuche erleichtern können.
    Aber auch auf "Macken und Eigenheiten" des Debuggers werde ich eingehen.

    Dieser Artikel bezieht sich auf Visual C++ 6 (SP6). Sie sollten aber eigentlich alles ohne Probleme auch in den Nachfolgerversionen finden können.
    Natürlich haben auch andere Entwicklungsumgebungen (auch von anderen Programmiersprachen) einen Debugger und vieles von hier werden Sie übertragen können.

    Englisch ist die Sprache aller Softwareentwickler, daher verwende ich überwiegend die englischen Fachausdrücke in diesem Artikel. Soweit möglich ergänze ich diese auch um die deutsche Übersetzung.
    __________________________________________________________________________________________________

    Was ist der Debugger?
    Der Debugger ist ein Teil von VC, der Ihnen hilft, Fehler zu finden. Sie können dem Programm direkt "bei der Arbeit" zuschauen.
    Da so ein Programm aber schnell sehr komplex wird, gibt es unzählige Hilfsmittel.
    __________________________________________________________________________________________________

    Haltepunkte / Breakpoints
    Ein Breakpoint ist eine Marke (roter Punkt), wo das Programm in der Ausführung unterbrochen wird, wenn die so markierte Anweisung an der Reihe ist.
    Sie können mit Hilfe von Breakpoints das Programm anhalten, sobald die markierte Zeile ausgeführt werden soll. Dann können Sie sich die aktuellen Variableninhalte anschauen oder auch, von wo diese Funktion gerade aufgerufen wurde.
    Das ist dann praktisch, wenn Sie eine bestimmte Stelle schon unter Verdacht haben.

    Sie können Breakpoints mit F9 setzen und auch wieder entfernen.
    Außerdem gibt es einen Dialog zum Verwalten aller Breakpoints; Sie erhalten ihn über

    • Strg + B
    • Alt + F9
    • Menü "Bearbeiten" -> "Haltepunkte..."

    Zusätzlich gibt es noch die Symbolleiste "Debug", die Sie über "Anpassen" noch um viele nützliche Schaltflächen erweitern können (und sollten).
    Da die Symbolleistenbefehle auch im Menü zu finden sind, werde ich sie ab jetzt nicht mehr erwähnen.

    Breakpoints funktionieren nur, wenn das Programm auch im Debugmodus gestartet wurde. Das geht mit

    • F5
    • Menü "Erstellen" - "Debug starten" -> "Ausführen"
    • Strg + F10 (Run to Cursor)

    Es gibt noch die Möglichkeit, den Debug mit F10 oder F11 zu starten, das bietet sich bei komplexen Programmen aber nicht an.

    Wenn es sinnvoller ist, können sie einzelne Breakpoints auch bedingt aktivieren.
    Das ist in dem Fall nützlich, wenn Sie sich beispielsweise eine for-Schleife erst ab dem 1000. Durchlauf ansehen möchten.
    Die Bedingung können Sie in dem "Breakpoint-Verwaltungs-Dialog" festlegen.
    Hier können Sie auch angeben, beim wievielten Mal "am Breakpoint vorbeikommen" das Programm erst angehalten werden soll.
    __________________________________________________________________________________________________

    Bewegungsmöglichkeiten in der Ausführung
    Da Sie ja sicherlich nicht nur den Zustand des Programms zu dem Zeitpunkt, wo der Breakpoint sitzt, beobachten wollen, können Sie auch schrittweise weitermachen.

    Das geht mit

    • F10: Eine Anweisung weiter
    • F11: In die Funktion springen
    • Shift+F11: Bis zum Rücksprung weiterlaufen
    • Strg + F10: Bis zur aktuellen Cursorposition weiterlaufen
    • und weiteren Befehlen im Menü "Debug"

    Welcher dieser Befehle angebracht ist, hängt von der jeweiligen Situation ab.
    Strg+F10 ist praktisch, wenn Sie zu der Stelle gelangen möchten, die Sie gerade betrachten z.B. um zu sehen, ob eine Bedingung so funktioniert, wie Sie sich das vorstellen.
    Im Prinzip würden sie mit F9 (Breakpoint setzen) und F5 (bis zum Breakpoint laufen) das Selbe erreichen, aber wenn Sie gerade beim Step by Step Debuggen sind, und in eine Schleife kommen, dann können Sie den Cursor mit den Pfeiltasten aus der Schleife bewegen und sind mit Strg + F10 schnell wieder aus der Schleife raus.

    Meistens werden Sie F10 nutzen, aber wenn Sie sehen möchten, was in einer Funktion mit ihren Variablen gemacht wird, dann brauchen Sie F11 und unter Umständen viel Geduld, denn für jeden Parameter laufen Sie erst einmal durch den passenden Konstruktor, bevor Sie in der Funktion landen, die Sie ansehen wollten.
    Für diesen Fall ist die Tastenkombination Shift+F11 sehr praktisch, da sie damit das Programm bis zum Rücksprung aus der aktuellen Funktion weiterlaufen lassen.

    Dafür hat dies aber den Vorteil, dass Sie nach und nach sehr viel über die innere Funktionsweise der MFC lernen, und je öfter Sie sich in die "Innereien" vorwagen, desto sicherer werden Sie sich bewegen können.
    __________________________________________________________________________________________________

    Aufrufliste / Callstack
    Der Callstack ist ein Unterfenster vom VC, in dem bei angehaltener Programmausführung gezeigt wird, welche Funktion welche mit was für Parametern aufgerufen hat, bis zu der Stelle, wo Sie gerade "stehen".
    Die aufgerufene Funktion steht immer über der aufrufenden.

    CMyApp::InitInstance() line 124
    AfxWinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 39 + 11 bytes
    WinMain(HINSTANCE__ * 0x00400000, HINSTANCE__ * 0x00000000, char * 0x00133ef5, int 1) line 30
    WinMainCRTStartup() line 330 + 54 bytes
    KERNEL32! 77e98989()
    

    Den Callstack können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Aufrufliste" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Aufrufliste" wählen
    • Alt + 7 drücken.

    Nicht alle Zeilen im Callstack sind normal lesbar. Einige sehen etwa so aus:

    KERNEL32! 77e98989()
    

    Darauf können Sie auch doppelklicken, aber Sie werden nur Assemblercode zu sehen bekommen. Für diese Funktionen gibt es keinen Quellcode.

    Der kleine grüne Pfeil am linken Rand im Editor zeigt, welche Befehlszeile gerade an der Reihe ist. Allerdings ist er nur dann auf dem aktuellen Befehl, wenn Sie in der allerobersten Funktion stehen.
    Wenn Sie per Doppelklick auf die gewünschte Zeile im Callstack in eine andere Funktion wechseln, dann zeigt er immer auf die Zeile, die als nächstes ausgeführt werden soll.
    __________________________________________________________________________________________________

    Gültigkeitsannahmen (ASSERT)
    ASSERTs dienen zur Prüfung, ob für die Programmausführung nötige Werte eingehalten werden. ASSERT ist "nur" ein Makro, wie auch TRACE und VERIFY. Sie erkennen das daran, dass es komplett groß geschrieben wird.
    ASSERT löst dann eine Meldung aus, wenn die Bedingung in den Klammern nicht erfüllt ist. Wollen Sie z.B. immer benachrichtigt werden, schreiben Sie

    ASSERT(FALSE);
    

    wogegen

    ASSERT(TRUE);
    

    ziemlich sinnlos ist.

    Sie sollten so beispielsweise Zeiger grundsätzlich prüfen:

    int* pInt = NULL;
    ASSERT(pInt); // für Schreibfaule
    ASSERT(pInt != NULL); // für Perfektionisten
    

    Dieser Code würde eine Assertion produzieren, die von Anfängern auch gerne als Fehlermeldung bezeichnet wird. Bei der Meldung haben Sie aber die Möglichkeit, "Wiederholen"/"Retry" zu drücken und werden genau auf der Zeile mit dem ASSERT landen, der die Meldung ausgelöst hat.
    Damit solche Prüfungen möglich sind, müssen Sie aber sorgfältig arbeiten, denn das hier würde keine Assertion auslösen:

    int* pInt; // Initialisierung vergessen
    ASSERT(pInt);
    

    So ist ASSERT eine gute Möglichkeit, sich um Fehler zu kümmern bevor das Programm abstürzt.
    Sie lassen sich einfach durch ASSERTs mitteilen, wenn das Programm wegen eines fehlerhaften Variableninhalts nicht mehr fehlerfrei ausgeführt werden kann.
    Tritt dieser Zustand ein, werden Sie als Entwickler darauf hingewiesen und können sich das Problem "am lebenden Objekt" ansehen.

    ⚠ ASSERT wird in der Release-Version durch "nichts" ersetzt.
    Daher dürfen Sie in den Klammern nichts schreiben, was für die Releaseversion wichtig ist.
    Also machen Sie bitte niemals sowas:

    ASSERT(SpeichereMeineDaten(CFile* f_datei));
    

    Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
    __________________________________________________________________________________________________

    Der ASSERT steht in MFC Quellcodes, was nun?
    Sie haben auf "Wiederholen" gedrückt und stehen nun mitten im fremden Quellcode?
    Dann gehen Sie doch im Callstack in die oberste Funktion, die von Ihnen ist und schauen Sie dort mal nach dem Rechten.
    Dort scheint alles okay? Hangeln Sie sich immer weiter in die jeweils aufrufende Funktion, denn irgendwo ist der Fehler schon versteckt.

    So ein ASSERT hat in der MFC entweder einen Kommentar oder er lässt sich fast wie im Klartext lesen. Ein

    ASSERT(IsWindow(...));
    

    zeigt Ihnen, dass Sie nach irgendwas suchen müssen, was mit einem Zugriff auf ein Fenster zu tun hat. Eine häufige Ursache ist, dass das Fenster noch nicht oder nicht mehr existiert.
    __________________________________________________________________________________________________

    Geprüfte Befehle (VERIFY)
    Sie können sich auch benachrichtigen lassen, falls eine Funktion einen bestimmten Wert zurückgibt.
    Sicher könnten Sie das auch mit ASSERT machen, aber das gäbe ja Probleme in der Release-Version.

    Gegenüber dem o.g. Beispiel ist es aber vollkommen okay,

    VERIFY(SpeichereMeineDaten(CFile* f_datei));
    

    zu schreiben.
    __________________________________________________________________________________________________

    Die Debugausgabe
    Wenn Sie noch unter DOS programmieren gelernt haben, werden Sie es noch kennen, dass man im Zweifelsfall kleine Ausgaben gemacht hat, um zu sehen, ob das Programm auch hinter den Kulissen tut, was es soll.
    Das geht z.B. bei einer SDI ja gar nicht mehr so einfach und außerdem hat sich Microsoft etwas praktisches einfallen lassen, was die Hilfsausgaben von den eigentlichen Ausgaben trennt: Die Debugausgabe.

    Die Debugausgabe können Sie einblenden, indem Sie

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Ausgabe" setzen
    • im Menü "Ansicht" - "Ausgabe" wählen
    • Alt + 2 drücken.

    Dort gibt die MFC schon automatisch Informationen aus und wie das geht lesen Sie gleich.
    __________________________________________________________________________________________________

    Hilfsausgaben (TRACE)
    Mit TRACE können Sie sich beliebige Daten in der Debugausgabe ausgeben lassen.
    Es erinnert in der Nutzung an printf bzw. CString::Format und ist deswegen sehr einfach zu handhaben.

    Wenn Sie sehen möchten, welchen Inhalt ihre Variablen haben, können Sie das ganz einfach ausgeben lassen:

    int nZahl = 10;
    CString strText = "Die Zahl ist";
    TRACE("%s: %d\n", strText, nZahl);
    

    Sie sollten das "\n" nicht vergessen, sonst haben Sie einen langen Text in der Ausgabe.

    Das eingebaute TRACE ist soweit ja sehr schön, es gibt aber ein paar Gründe, ein eigenes zu schreiben, was das Original nutzt, aber etwas mehr kann.

    • TRACE kann keine BOOL oder bool.
    • TRACE kann kein Datum.
    • Wollen Sie nachträglich noch in eine Datei tracen, ist es weniger Arbeit.
    • Sie können sich alles Mögliche einfallen lassen, was Ihnen das Leben erleichtert.

    __________________________________________________________________________________________________

    Weitere nützliche Makros
    Damit Sie Ihre Debugausgaben noch etwas verfeinern können, gibt es noch einige Makros:

    • __DATE__ : Das Datum, wann diese Zeile kompiliert wurde.
    • __FILE__ : Der Name der Datei, in der sich diese Zeile befindet.
    • __TIME__ : Der Zeitpunkt, wann diese Datei zuletzt compiliert wurde.
    • __LINE__ : Die Zeilennummer.

    Es gibt noch weitere Makros, schauen sie ruhig mal in die MSDN. Sie stehen alle in einem Kapitel.
    __________________________________________________________________________________________________

    Lokale Überwachung
    In der lokalen Überwachung finden Sie alle im aktuellen Zusammenhang bekannten Variablen und, wenn Sie sich mit F10 bewegen, auch die Rückgabewerte von gerade ausgeführten Funktionen.
    Hat sich ein Wert eben gerade geändert, wird er rot dargestellt.

    Die Überwachung können Sie einblenden, indem Sie im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Überwachung" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Überwachung" wählen
    • Alt + 3 drücken.

    __________________________________________________________________________________________________

    Variableninhalte überwachen
    In der Überwachung können Sie sich alle Variablen zusammensammeln, die Sie beobachten möchten.
    Dazu können Sie sie aus der lokalen Überwachung oder dem Quellcode per Drag&Drop herüber ziehen, den Namen eingeben oder aus dem Kontextmenü auf dem Variablennamen im Quellcode Schnellüberwachung (Quick Watch) wählen.

    Ist eine Variable nicht gültig, wird dies angezeigt, sonst ihr Inhalt.
    Sie sparen sich so die ständige Suche, wo denn welche Variable gerade in der lokalen Überwachung steht.
    Außerdem können Sie auch direkt nur Membervariablen von Klassen beobachten und müssen nicht ständig die Klasse aufklappen.

    Die Überwachung können Sie einblenden, indem man im Debugmodus

    • auf einer leeren Stelle einer Symbolleiste das Kontextmenü aufrufen und dort das Häkchen bei "Variablen" setzen
    • im Menü "Ansicht" - "Debug-Fenster" -> "Variablen" wählen
    • Alt + 4 drücken.

    __________________________________________________________________________________________________

    Sonderfälle beim Debuggen
    Sobald Sie die Anzeige Ihres Programms debuggen wollen, werden Sie schnell ein Problem haben.
    Ein Breakpoint in OnPaint oder OnUpdateCommandUI-Funktionen mutiert zu einer "Endlosschleife".
    OnCtlColor ist ebenfalls ein Problemfall, da es sehr häufig pro Neuzeichnen aufgerufen wird.
    Dies sind drei Beispiele, denen Sie häufiger begegnen werden.

    Um diese zu debuggen, müssen Sie Ihr Programm und das VC so auf dem Bildschirm platzieren, dass sie sich nicht überschneiden.
    Wer eine Mehrschirmlösung hat ist hier klar im Vorteil, besonders bei großen Fenstern.

    Der Grund dafür ist übrigens sehr einfach:
    Nehmen wir an, Sie haben einen Breakpoint in OnPaint.
    OnPaint wird immer aufgerufen, wenn das Fenster neu angezeigt werden muss. Es passiert nun folgendes:
    1.) Das Programm startet
    2.) Das Fenster wird angezeigt
    3.) Sie laufen auf den Breakpoint auf
    4.) Sie lassen das Programm mit F5 weiterlaufen
    5.) GOTO 2.

    OnUpdateCommandUI zu debuggen wird besonders bei Toolbarbuttons zur Qual, denn die werden ständig aktualisiert.
    __________________________________________________________________________________________________

    Endlosschleifen debuggen
    Ihr Programm hängt und Sie haben keine Ahnung, warum?
    Dann halten Sie es einfach an. Der Befehl dazu ist im Menü "Debug": "Anhalten"
    Nun können Sie sich so im Code bewegen, als wären Sie auf einen Breakpoint aufgelaufen.
    __________________________________________________________________________________________________

    Wenn Fehler beim Debug nicht mehr auftreten
    Sie wollen einen Fehler suchen, aber immer wenn Sie ihm im Debugger auf die Schliche kommen wollen, ist er weg?
    Dann ist es einer der schwierigeren Sorte.

    Eine Möglichkeit ist, dass einfach ein Neuzeichnen des Fensters nötig ist.
    Dann haben Sie irgendwo ein Invalidate vergessen. So einfach die Erklärung ist, man kann bei der Suche verzweifeln.

    Eine andere Möglichkeit ist, dass der Fehler nur auftritt, wenn das Programm mit normaler Geschwindigkeit läuft.
    Da Sie beim Debuggen immer wieder anhalten, werden Sie den Fehler auf diese Weise wohl nie finden. Dann sollten Sie zu TRACEs und evtl. auch bedingten Breakpoints greifen.
    __________________________________________________________________________________________________

    Ich habe einen Fehler in der MFC gefunden...
    Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, war ein Scherz.
    Die MFC ist zwar nicht fehlerfrei, aber normalerweise ist der Fehler in Ihrem Code zu finden, da die MFC doch sehr gründlich getestet wurde.

    Sollten Sie den Fehler trotz gewissenhafter Suche nicht finden, dann haben Sie verschiedene Möglichkeiten:

    • Lassen Sie erst einmal ein "Rebuild All" / "Alles neu erstellen" machen
    • Haben Sie schon den aktuellsten Service Pack für Ihr VC installiert? Nein? Dann holen Sie das nach.
    • Schauen Sie in die Liste, ob es doch ein Fehler in der MFC ist.

    Sollte das alles nicht helfen, haben Sie doch irgendwo einen Fehler in Ihrem Quellcode.
    __________________________________________________________________________________________________

    Der Debugger spinnt
    Breakpoints, die Sie setzen, landen ein paar Zeilen höher oder tiefer?
    Beim Debuggen werden Teile übersprungen, die eigentlich durchlaufen werden müssten, weil sie in keiner if oder Schleife stehen?
    Dann nehmen Sie sich eine Tasse Kaffee und wählen im Menü "Erstellen" - "Alles neu erstellen" (Rebuild All).
    Dieser Befehl sollte immer Ihre erste Wahl bei unerklärlichen Fehlern sein, denn leider ist Visual C++ nicht ganz fehlerfrei (oder wie Sie dieses Verhalten auch immer nennen wollen) und kommt manchmal durcheinander.

    Sollte das nicht geholfen haben, können Sie bei VC6 noch die ncb und die opt Datei löschen (vorher schließen Sie bitte das Projekt).
    Sollten Sie in der Klassenansicht mit Ordnern arbeiten, machen Sie vorher ein Backup, denn die Ordner sind dann weg.
    __________________________________________________________________________________________________

    Änderungen übernehmen und weiterdebuggen
    Ihnen wird von Microsoft die Möglichkeit gegeben, während des Debuggens den Code zu ändern und danach mit den vorgenommenen Änderungen weiterzuarbeiten.
    Auch Variableninhalte lassen sich ändern, das ist bei Schleifen manchmal praktisch, wenn Sie ein bestimmtes Verhalten provozieren wollen.
    Das klingt zwar alles ganz toll - aber ich persönlich vertraue dieser Technik nicht und starte immer wieder von vorne.
    __________________________________________________________________________________________________

    Debuggen der Release-Version
    Sie sind fertig, erstellen die Release-Version und... nichts geht mehr. In der Debug ist aber alles in Ordnung und den o.g. Artikel bei Codeproject haben Sie sich auch zu Herzen genommen?
    Dann können Sie in den Projekteinstellungen die Debuginfos zuschalten und die Release-Version ganz normal debuggen.
    Der feine Unterschied ist, dass jetzt aber _DEBUG nicht mehr definiert ist und die ganzen "Hilfestellungen" dadurch wegfallen. Falls Sie also unsauber programmiert haben, beispielsweise Zeiger nicht initialisiert haben, dann rächt sich das jetzt.
    Für mehr Informationen lesen Sie bitte auch diesen Artikel.

    Das Programm läuft bei Ihnen im Testbetrieb fehlerfrei, aber beim Kunden will es partout nicht laufen?
    Tja, dort können Sie nicht mal eben ein Visual C++ installieren und gucken was los ist.
    Bevor Sie nun per Remote-Debug auf Fehlersuche gehen, haben Sie noch eine Möglichkeit:
    Sie bauen im Programm kleine Messageboxen ein, die Ihnen an wichtigen Stellen Informationen geben (für so eine Suche brauchen Sie aber einen Kunden mit guten Nerven), oder Sie lassen die Infos in eine Datei ausgeben.
    Egal für was Sie sich an dieser Stelle entscheiden, es wäre sinnvoll gewesen, das schon von Anfang an vorzubereiten, indem Sie sich eigene Helferfunktionen (z.B. ein TRACE) geschrieben hätten, denn dann wäre eine Ausgabe in eine Datei nur ein geringer Aufwand.
    __________________________________________________________________________________________________

    Threads wechseln
    Oft laufen (auch wenn Sie das gar nicht eingebaut haben) mehrere Threads parallel ab. Hin und wieder landen Sie beim Unterbrechen im falschen und müssen wechseln.
    Das geht im Menü "Debug" - "Threads".
    Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
    __________________________________________________________________________________________________

    Bei Ausnahmen unterbrechen
    Sie sehen in der Debug-Ausgabe dauernd Zeilen wie

    Nicht abgefangene Ausnahme in ...
    

    können aber nicht finden, wo der Fehler ist?
    Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, wo Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:

    • Immer anhalten
    • Anhalten, wenn sie nicht behandelt wurde

    __________________________________________________________________________________________________

    Das Debuggen beenden
    Wenn Sie das Programm vorzeitig beenden wollen, etwa weil Sie ohne den Debugger den Code ändern möchten oder es auch einfach nicht mehr stabil laufen will, dann können Sie das mit "Debug beenden" im Menü Debug oder mit Shift + F5.
    __________________________________________________________________________________________________

    Was Sie noch gelesen haben sollten



  • Danke Polofreak. 🙂

    Das Strg+F10 habe ich jetzt hoffentlich ausreichend nachgepflegt - du hast Recht, das ist praktisch. Aber ich kannte es nicht. 🙄
    Bei dem Makro-Kapitel weiß ich leider nichts mehr. Wenn du etwas zurückguckst, wirst du sehen, dass es noch nicht lange dabei ist. Ich wollte es nur erwähnt haben, damit man weiß, dass es da noch mehr gibt.

    Und für das Nörgeln wurdest du ja schließlich "angestellt". 😉 (Wenn man es "konstruktive Kritik" nennt, klingt es schon gar nicht mehr so negativ.)

    Schau bitte nochmal kurz drüber, ob du mit den Änderungen zufrieden bist. 🙂
    ... ich sehe gerade, ich habe das Markieren vergessen, das hole ich noch bestmöglich nach. 🙄 ...so, fertig. 🙂



  • Also,
    der Artikel ist ja schon sehr gut und Umfangreich geworden.
    Allerdings würde ich das mit dem VERIFY noch ein wenig ausführlicher beschreiben, da mir bis jetzt nur klar ist das man sich hier eine Benachrichtigung für einen bestimmten Wert zurück bekommt.
    Ebenfalls wäre es klasse wenn man so ne kleine eigene Klasse zum Tracen erstellen würde.
    Vielleicht wäre es noch gut ein Paar Screenshots mit einzubauen, da dies bei vielen zum Verständnis beitragen würde.


Anmelden zum Antworten