Debuggen mit VC++6
-
1 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.
2 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.3 Haltepunkte / Breakpoints
Ein Breakpoint ist eine Marke , an der 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).
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.
4 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, um z.B. 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) dasselbe 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 sich ansehen wollen.
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.
5 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 bis zu der Stelle aufgerufen hat, 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.6 Gültigkeitsannahmen (ASSERT)
ASSERTs dienen zur Prüfung, ob die für die Programmausführung nötigen 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 SieASSERT(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, die 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 so etwas:ASSERT(SpeichereMeineDaten(CFile* f_datei));
Dazu lesen Sie aber am besten auch Surviving the Release Version bei Codeproject.
6.1 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.7 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 in Ordnung,
VERIFY(SpeichereMeineDaten(CFile* f_datei));
zu schreiben.
8 Die Debugausgabe
Wenn Sie noch gelernt haben, unter DOS zu progammieren, 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.
8.1 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.
8.2 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.
9 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.
9.1 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überziehen, den Namen eingeben oder aus dem Kontextmenü auf dem Variablennamen im Quellcode Schnellüberwachung (Quick Watch) wählen.
Wenn Sie nur "mal eben" die Variable anschauen wollen, reicht oft auch der Tooltip aus.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 Sie 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.
10 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.
10.1 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.10.2 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.11 Ich habe einen Fehler in der MFC gefunden...
Oh, Sie haben echt was drauf - bewerben Sie sich schnell bei Microsoft... äh nein, Scherz beiseite.
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.
12 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 keinem if oder keiner 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.13 Ä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.14 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.15 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" oder mit
Doppelklicken Sie in dem Dialog einfach auf den gewünschten Thread und Sie werden an die aktuelle Position im Quellcode gebracht.
16 Bei Ausnahmen unterbrechen
Sie sehen in der Debug-Ausgabe dauernd Zeilen wie
Nicht abgefangene Ausnahme in ...
können aber den Fehler nicht finden?
Dann können Sie im Menü "Debug" mit "Ausnahmen..." ein Dialogfeld aufrufen, in dem Sie einstellen können, wie sich der Debugger bei welcher Ausnahme verhalten soll:- Immer anhalten
- Anhalten, wenn sie nicht behandelt wurde
17 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, mit oder mit Shift + F5.
18 Was Sie noch gelesen haben sollten
- jQT- Debuggen im Borland C++ Builder
Eine sehr schön erklärte und bebilderte Einführung, die man mit wenigen Anpassungen für Visual C++ übernehmen kann. (Danke @ junix) - Artikel zum Debuggen bei Codeproject
Codeproject ist wohl die Anlaufstelle für ratlose oder Ideen suchende Entwickler. Und natürlich haben sie auch etwas zum Thema Debuggen zu bieten. - Verwenden des Debuggers in der MSDN
- Debuggen der Release-Version
Eine Erklärung mit einigen guten Links. Der Artikel ist auf jeden Fall lesenswert. (Danke @ Redhead) - Remote-Debug
Eine Schritt für Schritt Anleitung.
-
Gute Zusammenfassung zum was, warum, und wie des Debuggens von Programmen
unter VC6.Vielleicht erübrigen sich damit ja einige Fragen der Newcomer die, wenn man
die Fragen sieht, noch nie was von diesem Hilfsmittel gehört haben.
Ich will ja mal nicht jedem Fall Faulheit unterstellen.
-
Sehr, sehr gewöhnungsbedürftige Einstellungen des Aussehens der Fenster. Lila Hintergrund mit Schreibschrift. Also ich persönlich find das schrecklick!
Was hast du bloß für Einstellungen, estartu_de?Mr. B
-
Ich dachte es mir auch schon, ich habe zwar keine guten Erinnerungen an den VC++ 6, aber dass er derartig ... ungewöhnlich ... war, daran konnte ich mich auch nicht mehr erinnern.
Alles in allem ein sehr praxisnaher Artikel, der vor allem die Grundlagen IMHO sehr großflächig abdeckt. In einem Foren-Archiv macht er sich sicher gut, bei einem Print-Magazin hätte ich persönlich höhere Ansprüche bzgl. der Kenntnis des Lesers vorausgesetzt. Aber in der Form sicherlich ideal.
-
Hallo,
mal ne (dumme?) Frage:
Ich finde diesen Beitrag sehr toll, da ich bisher wirklich nix mit dem Debugger anfangen konnte. Aber ASEERT Und VERIFY werden leider nicht erkannt
(Ich benutze MS Viasual c++ standard)Muss ich da noch was inkludieren? Und werden diese ASSERT bei der Release Version automatisch entfernt oder muß ich nor irgendwo was einstellen?
-
ich glaube ein #include <assert.h> hilft hier
-
Das ist kein Lila, das ist Blau. :p Bei Autos finden das alle sooooo cool.
@Anfänger: Du musst nichts mehr einstellen, das ist automatisch so.
-
estartu_de schrieb:
Das ist kein Lila, das ist Blau. :p Bei Autos finden das alle sooooo cool.
na hoffentlich hat das kennzeichen deines VW busses nicht SO eine schriftart, sonst müssten die armen polizisten ja augenkrebs kriegen...
Mr. B
-
Tach, ich habe auch noch eine Frage zum Debuggen.
ich habe folgendes Programm mit dem Debugger gestartet:
#include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain( HINSTANCE hInstance, //Identifikationswert HINSTANCE hPrevInstance, //Veraltet und unnötig es nochmals aufzurufen PSTR szCMDLine, //Kommandozeilenpararmeter int iCmdShow //Kontrolliert das Aussehen des fensters ) { static TCHAR szAppName[] = TEXT("Hello"); HWND hwnd; //Handle für ein Fenster MSG msg; //Nachrichten WNDCLASS wndclass; //Fensterklasse wndclass.style = CS_HREDRAW | CS_VREDRAW; //Klassenstile wndclass.lpfnWndProc = WndProc; //Funktion für Nachrichtenauswertung wndclass.cbClsExtra = 0; //Zusatzspeicher im Rahmen von Fensterklassen wndclass.cbWndExtra = 0; //Zusatzspeicher in Fenstern wndclass.hInstance = hInstance; //eigene Instanz Kennziffer won WinMain wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); //Lädt das Icon wndclass.hCursor = LoadCursor (NULL, IDC_ARROW); //Lädt den Curser wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);//Hintergrundfarbe setzen wndclass.lpszMenuName = NULL; //Festlegen des Menüs wndclass.lpszClassName = szAppName; //Namen der Fensterklasse if (!RegisterClass(&wndclass)) //definiert die angegebene Fensterklasse { MessageBox ( NULL, TEXT ("Programm arbeitet mit Unicode und braucht Windows: 2000, NT, XP, oder neuer!"), szAppName, MB_ICONERROR ); return 0; } hwnd = CreateWindow ( //erzeugt ein Fenster auf Basis einer Fensterklasse szAppName, //Name der FensterKlasse TEXT ("Nur ein Fenster"), //Fenstertitel WS_OVERLAPPEDWINDOW, //Fensterstil CW_USEDEFAULT, //X-Position des Fensters CW_USEDEFAULT, //Y-Position des Fensters CW_USEDEFAULT, //Fensterbreite CW_USEDEFAULT, //Fensterhöhe NULL, //Übergeordnetes Fenster NULL, //Menü hInstance, //Programm-Kopiezähler (Programm-ID) NULL //zusätzliche Prarmeter ); ShowWindow (hwnd, iCmdShow); //stellt das Fenster auf dem Bildschirm da UpdateWindow(hwnd); //Fenster soll seinen Inhallt darstellen while (GetMessage (&msg, NULL, 0, 0)) //Holt Nachricht aus der Warteschlange { TranslateMessage(&msg); //setzt Tastaturnachrichten um DispatchMessage(&msg); //senden der Windows_Prozedur Nachrichten } return msg.wParam; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rect; switch (message) { case WM_CREATE: return 0; case WM_PAINT: hdc = BeginPaint (hwnd, &ps); //Teilt Windows mit, das mit Zeichnen begonnen wird GetClientRect (hwnd, &rect); //Ermitteld die Größed es Anwendungsbereichs des Fensters DrawText(hdc, TEXT("Hallo"),-1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint (hwnd, &ps); //Teilt Windows mit, das mit Zeichnen beendet wird return 0; case WM_DESTROY: PostQuitMessage (0); //setzt Nachricht "Ende" in Nachrichtenwarteschlange return 0; } return DefWindowProc (hwnd, message,wParam, lParam);//Bearbeitet nicht abgefangene Nachrichten }
Das Programm ist fehlerfrei (denke ich mal). Wenn ich den Debugger aber anhalte wird mir folgendes angezeigt:
7C91EB94 ret
7C91EB95 lea esp,[esp]
7C91EB9C lea esp,[esp]
7C91EBA0 nop
7C91EBA1 nop
7C91EBA2 nop
7C91EBA3 nop
7C91EBA4 nop
7C91EBA5 lea edx,[esp+8]
7C91EBA9 int 2Eh
7C91EBAB ret
7C91EBAC push ebp
7C91EBAD mov ebp,esp
7C91EBAF pushfd
7C91EBB0 sub esp,2D0h
7C91EBB6 mov dword ptr [ebp-224h],eax
7C91EBBC mov dword ptr [ebp-228h],ecx
7C91EBC2 mov eax,dword ptr [ebp+8]
7C91EBC5 mov ecx,dword ptr [ebp+4]
7C91EBC8 mov dword ptr [eax+0Ch],ecx
7C91EBCB lea eax,[ebp-2D4h]
7C91EBD1 mov dword ptr [eax+0B8h],ecx
7C91EBD7 mov dword ptr [eax+0A4h],ebx
7C91EBDD mov dword ptr [eax+0A8h],edx
7C91EBE3 mov dword ptr [eax+0A0h],esi
7C91EBE9 mov dword ptr [eax+9Ch],edi
7C91EBEF lea ecx,[ebp+0Ch]
7C91EBF2 mov dword ptr [eax+0C4h],ecx
7C91EBF8 mov ecx,dword ptr [ebp]
7C91EBFB mov dword ptr [eax+0B4h],ecx
7C91EC01 mov ecx,dword ptr [ebp-4]
7C91EC04 mov dword ptr [eax+0C0h],ecx
7C91EC0A mov word ptr [eax+0BCh],csWarum wird ncht mein Code gezeigt. Außerdem, wenn ich in einzelschritten fortsetzen nmache, geht das vielleicht ca. 20 mal. Danach weigert sich der Debugger überhaupt was zu tun!
Bei linearen Konsolenanwendung zeigt er mir aber ganz normal meinen code und auch sonst ist dort alles in ordnung!
Mache ich etwas falsch?
-
MisterX schrieb:
Warum wird ncht mein Code gezeigt. Außerdem, wenn ich in einzelschritten fortsetzen nmache, geht das vielleicht ca. 20 mal. Danach weigert sich der Debugger überhaupt was zu tun!
Bei linearen Konsolenanwendung zeigt er mir aber ganz normal meinen code und auch sonst ist dort alles in ordnung!
Mache ich etwas falsch?
Also, ich vermute, du bist irgendwo in der API gelandet. Für die gibt es wohl keinen Quellcode (zumindest wurde er nicht gefunden) und deswegen bekommst du nur Assemblercode.
Warum er sich aufhängt, weiß ich nicht.
Läuft es denn normal, wenn du es nicht anhälst?Wie sieht dein Callstack aus? Findest du dort deinen Code?
-
MisterX schrieb:
Außerdem, wenn ich in einzelschritten fortsetzen nmache, geht das vielleicht ca. 20 mal. Danach weigert sich der Debugger überhaupt was zu tun!
Kanns sein, dass du dich in einer Schleife befindest, die vielleicht ca. 20 mal durchlaufen wird und dann das Programm bis zum Ende ausgeführt wird?
-
Tach,
ich habe das Programm (Das definitiv OK ist) jetzt auch mal mit dem Debugger getestet.
Es passiert folgendes:
Das Programm erzeugt das Fenster. Nun drückst du die Taste zum Stoppen des Debuggers. Das Fenster wird also in den Hintergrund gestellt und erhält (weil der Debugger im Vordergrund ist) keine WM_PAINT Befehle.
Also befindet man sich nur noch in der Nachrichten schleife, die ohne was zu tum abgearbeitet wird. Danach kommen keine weiteren Nachrichten. Komischerweise verträgt der Debugger das nicht und stürtzt ab - hab ich mit dem Visual c++ 6.0 Compiler (Mit den neuesten Updates getestet).
Das Programm ist definitiv noch nicht beendet, da das Fenster wieder in den Vordergrund geholt werden kann. Der Debugger reagiert aber nicht mehr!Wenn du allerdings das Fenster wieder in den Vordergrund holst und somit ein Neuzeichnen erzeugst BEVOR du die Einzelschritte ausführst stürzt er NICHT bei ca 20 Weiteren Einzelschritten ab.
Deine (komische Ausgabe) ist Assemblercode. Das kommt bei mir auch. Du befindest dich dann in irgendeiner Dll. Wahrscheinlich eine die irgendwie für die Nachrichtenauswertung zuständig ist.
Und genau dort stürtzt der Debugger auch bei mir ab!77D4F628 nop 77D4F629 nop 77D4F62A mov edi,edi 77D4F62C push ebp 77D4F62D mov ebp,esp 77D4F62F sub esp,0Ch 77D4F632 mov eax,dword ptr [ebp+8] 77D4F635 push dword ptr [eax+10h] 77D4F638 and dword ptr [ebp-8],0 77D4F63C and dword ptr [ebp-4],0 77D4F640 lea ecx,[eax+8] 77D4F643 push ecx 77D4F644 push dword ptr [eax+4] 77D4F647 push dword ptr [eax] 77D4F649 call dword ptr [eax+14h] 77D4F64C push 0 77D4F64E push 0Ch 77D4F650 pop edx 77D4F651 lea ecx,[ebp-0Ch] 77D4F654 mov dword ptr [ebp-0Ch],eax 77D4F657 call 77D194A4 //genau hier ist der Absturz 77D4F65C leave 77D4F65D ret 4 77D4F660 nop 77D4F661 nop 77D4F662 nop 77D4F663 nop 77D4F664 nop 77D4F665 mov edi,edi 77D4F667 push ebp 77D4F668 mov ebp,esp 77D4F66A push 1 77D4F66C push dword ptr [ebp+18h] 77D4F66F push dword ptr [ebp+14h] 77D4F672 push dword ptr [ebp+10h] 77D4F675 push dword ptr [ebp+0Ch] 77D4F678 push dword ptr [ebp+8] 77D4F67B call 77D3082F 77D4F680 pop ebp 77D4F681 ret 14h
PS. Ich kenne mich mit Assembler nicht so aus.
Was macht genau diese Anweisung bei der es schief geht?
-
Nun drückst du die Taste zum Stoppen des Debuggers.
Warum das denn??? Kein Wunder das du in den Tiefen des Betriebssystems landest. Das ist ja schon wie Roulettspielen.
Mach mal nen Breakpoint in deinen Sourcecode rein (z.B. in einen Ereignis-Einsprung). Und wenn du das Ereignis ausführst (z.B. durch drücken eines Buttons), wird dein Programm an der Stelle des Breakpoints unterbrochen.
-
Das ist auch nur für den Fall gedacht, dass man mit den schon erwähnten Hilfsmitteln nicht mehr weiter kommt.
Wenn ich gar keine Ahnung habe, wo das Problem liegt, dann drücke ich Pause und gucke dann im Callstack. So habe ich noch alles gefunden.Manchmal sind die Fehler nämlich nicht da, wo man denkt.
-
wie wärs mit einer PDF Version des Artikels? ist so etwas umständlich zum ausdrucken
-
Memory-Breakpoints sollten auch noch erwähnt sein: