vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Tag E

Debugger und Profiler

Informationen zum Debuggen und Durchsuchen erzeugen

Ein großer Teil der Anwendungsentwicklung besteht eigentlich aus der Fehlersuche im Programm. Die gesamte Softwareentwicklung ist ein straffer Zyklus von Anwendungsentwurf, Implementierung und Fehlersuche.

Visual C++ bietet eine umfangreiche Umgebung für die Fehlersuche und zahlreiche Debugging-Werkzeuge, die bei der Programmentwicklung unentbehrlich sind. Man kann damit Probleme schnell erkennen, den Inhalt von Variablen überwachen und den Programmfluß durch den eigenen und den MFC-Code verfolgen.

Werkzeuge wie das Programm Spy++ zeigen, welche Nachrichten Windows an die Anwendung übergibt, und man kann auch untersuchen, mit welchen Steuerelementen der Benutzeroberfläche und welchen Windows-Stilen die Anwendungen arbeiten.

Die Modi Debug und Release

Der Compiler unterscheidet zwei Hauptmodi, die man für das Erstellen der Anwendung konfigurieren kann: Debug und Release. Diese Modi lassen sich ändern, indem man im Menü Projekt den Befehl Einstellungen wählt oder (Alt)+(F7) drückt. Es erscheint das Dialogfeld Projekteinstellungen (siehe Abbildung E.1). Die Haupteinstellungen des Projekts sind auf oberster Ebene zu sehen und lassen sich ändern, indem man eine der im Kombinationsfeld aufgeführten Optionen markiert. Ist eine Einstellung markiert, beziehen sich alle Änderungen, die Sie an den Optionen auf den Registerkarten im rechten Teil des Dialogfelds vornehmen, auf diese Konfiguration. Wenn Sie die Anwendung erstellen, gelten dabei die aktuellen Konfigurationseinstellungen. Haben Sie Alle Konfigurationen gewählt, wirken die Änderungen für alle Konfigurationen gleichzeitig.

Abbildung E.1:
Die Registerkarte C/C++ des Dialogfelds Projekteinstellungen

Sobald Sie ein neues Projekt anlegen, stehen sowohl die Release- als auch die Debug- Konfiguration zur Verfügung. Die beiden Modi erzeugen unterschiedlichen Objektcode. In der Konfiguration für den Debug-Modus entsteht ein großes und relativ langsames ausführbares Programm. Das hängt damit zusammen, daß eine Unmenge von Debugging-Informationen in das Programm eingebunden werden und alle Optimierungen des Compilers deaktiviert sind.

Wenn Sie dasselbe Programm im Release-Modus erneut kompilieren, erhalten Sie ein kleines und schnell laufendes Programm, können aber nicht den Quellcode im Schrittbetrieb durchgehen oder irgendwelche Debugging-Meldungen erhalten.

Während der Entwicklungsphase einer Anwendung läßt man den Compiler normalerweise im Debug-Modus, damit man Probleme im Code schnell und einfach einkreisen und beseitigen kann. Haben Sie die Anwendung fertiggestellt und bereiten sie für den Vertrieb vor, können Sie die Konfiguration auf Release-Modus stellen und ein kleines, schnelles Programm für die Benutzer produzieren.

Testen im Release-Modus

Nachdem Sie die Anwendung im Release-Modus erstellt haben, sollten Sie sie umfassend testen, bevor sie in den Vertrieb geht. Fehler können beispielsweise dadurch entstehen, daß man maßgeblichen Programmcode in ASSERT-Makros (weiter hinten in diesem Anhang behandelt) beibehält. Im Release-Modus werden aber diese Makros entfernt. Darüber hinaus können auch Probleme aufgrund von Geschwindigkeits- oder Speicheroptimierungen auftauchen.

Optionen und Stufen für das Debuggen

Auf der Registerkarte C/C++ des Dialogfelds Projekteinstellungen lassen sich verschiedene Optionen und Stufen für das Debuggen einstellen. Diese Dialogseite erreichen Sie über den Befehl Einstellungen des Menüs Projekt (oder durch Drücken von (Alt)+(F7)). Im Dialogfeld Projekteinstellungen aktivieren Sie dann die Registerkarte C/C++.

In der Kategorie Allgemein sind folgende Elemente verfügbar:

Auf dieser Stufe führt sogar der vom Microsoft-eigenen Anwendungs-Assistenten generierte Code zu Warnmeldungen (auch wenn es sich nur um nicht genutzte Funktionsparameter handelt, die man getrost ignorieren kann).

int a = b * c / d + e;
#ifdef _DEBUG
CString strMessage;
strMessage.Format("Zwischenergebnis (Summe): %d", a);
AfxMessageBox(strMessage);
#endif

Tabelle E.1: Warnstufen des Compilers

Stufe

Ausgegebene Warnung

Keine

Keine

Stufe 1

Nur die schwerwiegendsten.

Stufe 2

Einige weniger schwerwiegende.

Stufe 3

Standardstufe (alle sinnvollen Warnungen).

Stufe 4

Sehr empfindlich (gut für Perfektionisten).

Tabelle E.2: Einstellungen der Debug-Info

Einstellung

Generierte Debug-Infos

Keine

Produziert keine Debug-Informationen - normalerweise für die Release-Modi vorgesehen.

Nur Zeilennummern

Generiert nur Zeilennummern, die sich auf den Quellcode beziehen, für Funktionen und globale Variablen. Allerdings verringern sich die Erstellungszeit und die Größe der ausführbaren Datei.

C7-kompatibel

Generiert Debug-Informationen, die mit Microsoft C 7.0 kompatibel sind. Schreibt alle Debug-Informationen in die ausführbaren Dateien, vergrößert sie damit, erlaubt aber ein komplett symbolisches Debuggen.

Programmdatenbank

Diese Einstellung erzeugt eine Datei mit der Erweiterung .pdb, die alle verfügbaren Debug-Informationen aufnimmt, aber keine Informationen zum Bearbeiten und Fortsetzen erzeugt.

Programmdatenbank für »Bearbeiten und Fortsetzen"

Die standardmäßige und übliche Debug-Einstellung. Sie produziert eine .pdb-Datei mit der höchsten Debug-Stufe und erzeugt die notwendigen Informationen für das Merkmal Bearbeiten und Fortsetzen.

Browse-Infos

Mit dem Hilfsprogramm Quellcode-Browser können Sie Ihren Quellcode im Detail inspizieren. Dieses Werkzeug ist unschätzbar, wenn Sie den Code eines anderen Programmierers untersuchen oder zu Ihrem eigenen Werk zurückkehren, mit dem Sie eine ganze Zeitlang nichts zu tun hatten.

Um das Programm zu starten, drücken Sie (Alt)+(F12) oder wählen im Menü Extras den Befehl Quellcode-Browser. (Wenn Sie das erste Mal dieses Programm aufrufen, erscheint die Frage, ob Sie das Projekt neu erstellen möchten, um die Browse-Informationen zu generieren.)

Das erste Dialogfeld des Quellcode-Browsers fragt nach einem Bezeichner, nach dem zu suchen ist (wie es Abbildung E.2 zeigt). Dabei kann es sich um den Namen einer Klasse, einer Struktur, einer Funktion oder globalen oder lokalen Variablen in Ihrer Anwendung handeln. Nachdem Sie einen Bezeichner eingegeben haben, ist die Schaltfläche OK aktiviert, und Sie können nach Einzelheiten zu diesem Bezeichner fahnden.

Abbildung E.2:
Das Dialogfeld Durchsuchen fordert ein zu suchendes Symbol an.

Im Listenfeld Abfrage auswählen finden Sie verschiedene Optionen bezüglich der Details, die zum gewählten Symbol gehören. Es stehen folgende Optionen zur Wahl:

Remote- und Just-In-Time-Debugging

Zum Debugger gehören Werkzeuge, mit denen man ein laufendes Programm auf einem Remote-Computer (sogar über das Internet via TCP/IP) debuggen kann. Das ist hilfreich, wenn man die Anwendung in einer anderen Umgebung als der Entwicklungsmaschine testen möchte. Dazu müssen Sie über genau die gleichen Versionen der .dll- und .exe-Dateien auf beiden Maschinen verfügen. Nach dem Laden des Projekts, können Sie es über ein gemeinsam genutztes Verzeichnis von der Remote- Maschine aus debuggen, indem Sie im Dialogfeld Projekteinstellungen auf der Registerkarte Debug in das Eingabefeld Ausführbares Programm für Debug-Sitzung den Pfad und Dateinnamen der lokalen .exe-Datei eintragen.

Der Pfad zur .exe-Datei ist auch in das Eingabefeld Pfad und Name des ausführbaren Remote-Programms im unteren Teil der Registerkarte Debug einzutragen. Das Eingabefeld Arbeitsverzeichnis lassen Sie dabei leer. Dann können Sie den Monitor des Remote-Debuggers auf dem Remote-Computer starten, indem Sie das Programm MSVCMON.EXE aufrufen. Eine Verbindung zum Remote-Computer stellen Sie über den Befehl Remote-Verbindung des Debuggers im Menü Erstellen her.

Im Dialogfeld Remote-Verbindung können Sie als Verbindung Lokal für eine Sitzung mit gemeinsam genutztem Verzeichnis oder Netzwerk (TCP/IP) beim Debuggen über eine TCP/IP-Verbindung wählen. (Um die Adresse festzulegen, klicken Sie auf die Schaltfläche Einstellungen.) Damit wird eine Verbindung zum Remote-Monitor hergestellt, der die Remote-Debugging-Sitzung startet.

Die Dateien für den Remote-Debugger installieren

Um den Remote-Debugger-Monitor auf der Remote-Maschine auszuführen, sind die folgenden Dateien erforderlich: MSVCMON.EXE, MSVCRT.DLL, TLN0T.DLL, DM.DLL, MSVCP50.DLL und MSDIS100.DLL. Die Dateien finden Sie in ihrem installierten Unterverzeichnis ...\Microsoft Visual Studio\Common\MSDev98\bin bzw. in \Windows\System (die Datei MSVCP50.DLL).

Beim Just-In-Time-Debugging können Sie ein Programm debuggen, das normal (nicht über den Debugger) läuft und dann ein Problem verursacht. Wenn Sie Visual C++ auf einem Computer installiert und diese Option aktiviert haben, wird jedes Programm, das einen Fehler verursacht, in eine neue Sitzung des Visual Studios zum Debuggen geladen und zeigt den Code, der den Absturz verursacht hat.

Lustig wird es, wenn Visual Studio selbst abstürzt, daraufhin eine weitere Sitzung von sich selbst lädt und eine Assemblercode-Ansicht zeigt, wo der Absturz im Original stattgefunden hat, damit Sie sich auf die Fehlersuche machen können. Es kann sehr hilfreich sein, die eigenen Anwendungen auf Fehler zu untersuchen, wenn sie unerwartet abstürzen (normalerweise bei einer Vorführung für Ihren Chef). Um diese Option einzuschalten, wählen Sie aus dem Menü Extras den Befehl Optionen. Im Dialogfeld Optionen gehen Sie auf die Registerkarte Debug und schalten das Kontrollkästchen Just-In-Time-Debugging ein.

Das Kontrollkästchen OLE RPC-Debugging auf dieser Registerkarte ist ebenfalls sehr hilfreich, wenn Sie COM- und DCOM-Anwendungen entwickeln, weil der Debugger damit in einen prozeßexternen Funktionsaufruf von Programmen oder DLLs springen und eine zweite Instanz des Debuggers starten (oder aktivieren) kann, um den anderen Prozeß zu verarbeiten. Die Steuerung kehrt dann zurück, wenn die Rückkehr aus der Remote-Funktion stattfindet. Das Ganze funktioniert in Netzwerken und mit verschiedenen Computern.

Ablaufverfolgung und Einzelschrittausführung

Eines der nützlichsten Features der Debugging-Umgebung von Visual C++ ist der interaktive Einzelschrittbetrieb. Damit läßt sich der Code zeilenweise ausführen und der Inhalt der Variablen untersuchen. Außerdem kann man Haltepunkte setzen, so daß das Programm bis zu diesem Haltepunkt läuft und dort stoppt. Von hier aus kann man im Einzelschrittbetrieb arbeiten, bis man das Programm normal fortsetzen möchte.

Trace- und Assertion-Anweisungen unterstützen ebenfalls die Suche nach Programmfehlern. Mit Trace-Anweisungen kann man Meldungen und Variablen aus dem Programm heraus im Ausgabefenster anzeigen, während das Programm durch diese Trace-Anweisungen läuft. Assertions erlauben es, das Programm anzuhalten, wenn eine Bedingung nicht erfüllt ist, obwohl sie eigentlich erfüllt sein müßte.

Das Makro TRACE

TRACE-Makros können Sie an den verschiedensten Stellen in Ihr Programm einbauen, um etwa zu kennzeichnen, daß bestimmte Teile des Codes ausgeführt wurden, oder um den Inhalt von Variablen an diesen Punkten anzuzeigen. Die TRACE-Makros werden in der Debug-Konfiguration in den Code kompiliert und im Ausgabefenster auf der Registerkarte Debug angezeigt, wenn Sie das Programm über den Debugger ausführen.

Die TRACE-Makros brauchen Sie für die Release-Version des Programms nicht zu entfernen, da sie der Compiler beim Erstellen automatisch aus dem Zielobjekt ausschließt.

Mit einem Formatstring als erstem Parameter an das TRACE-Makro können Sie einfache Meldungen anzeigen oder den Inhalt von Variablen ausgeben. Der Formatstring entspricht im Aufbau völlig den Strings, die man an die Funktionen printf oder CString::Format übergibt. Es lassen sich verschiedene spezielle Formatcodes festlegen wie %d zur dezimalen Anzeige einer Zahl, %x zur hexadezimalen Anzeige einer Zahl oder %s zur Anzeige von Strings. Die darauffolgenden Parameter müssen dann in der entsprechenden Reihenfolge der Formatcodes stehen. Beispielsweise bewirkt der Code

int nMyNum = 60;
char* szMyString = "Mein String";
TRACE("Zahl = %d, oder %x (hex). Der String lautet: %s\n",
nMyNum, szMyString);

die Ausgabe der folgenden Trace-Zeile:

Zahl = 60, oder 3c (hex). Der String lautet: Mein String

Listing E.1 demonstriert, wie man mit dem Makro TRACE den Inhalt eines Arrays vor und nach dem Sortieren durch einen zwar nicht sehr effizienten aber einfachen Sortieralgorithmus anzeigt.

Wenn Sie den in Listing E.1 dargestellten Code ausprobieren möchten, erstellen Sie mit dem Anwendungs-Assistenten das Gerüst einer einfachen SDI-Anwendung. Fügen Sie den Code über der Elementfunktion OnNewDocument der Dokumentklasse hinzu, und rufen Sie ihn über DoSort() in Ihrer Funktion OnNewDocument auf.

Die Anwendung können Sie über den Debugger ausführen, um die Ausgaben zu verfolgen. (Wählen Sie Erstellen / Debug starten / Ausführen.)

Stellen Sie sicher, daß das Ausgabefenster sichtbar ist (beispielsweise durch Wahl von Ansicht / Ausgabe), und aktivieren Sie hier die Registerkarte Debug.

Listing E.1: DebTestDoc.cpp - Eine einfache Sortierroutine, die die Verfahren zum Debuggen demonstriert

1: void Swap(CUIntArray* pdwNumbers,int i)
2: {
3: UINT uVal = pdwNumbers->GetAt(i);
4: pdwNumbers->SetAt(i, pdwNumbers->GetAt(i+1));
5: pdwNumbers->SetAt(i+1,uVal);
6: }
7:
8: void DoSort()
9: {
10: CUIntArray arNumbers;
11: for(int i=0;i<10;i++) arNumbers.Add(1+rand()%100);
12:
13: TRACE("Vor dem Sortieren\n");
14: for(i=0;i<arNumbers.GetSize();i++)
15: TRACE("[%d] = %d\n",i+1,arNumbers[i]);
16:
17: BOOL bSorted;
18: do
19: {
20: bSorted = TRUE;
21: for(i=0;i<arNumbers.GetSize()-1;i++)
22: {
23: if (arNumbers[i] > arNumbers[i+1])
24: {
25: Swap(&arNumbers,i);
26: bSorted = FALSE;
27: }
28: }
29: } while(!bSorted);
30:
31: TRACE("Nach dem Sortieren\n");
32: for(i=0;i<arNumbers.GetSize();i++)
33: TRACE("[%d] = %d\n",i+1,arNumbers[i]);
34: }

Listing E.1 sortiert ein Array von Zufallszahlen (zwischen 1 und 100), die in Zeile 11 generiert werden. Die Zeilen 13 bis 15 geben den Inhalt des Arrays vor dem Sortieren mit Hilfe von TRACE-Anweisungen aus. Die Zeilen 17 bis 29 sortieren das Array, indem paarweise aufeinanderfolgende Zahlen ausgetauscht werden, wenn sie nicht der geforderten Sortierrichtung entsprechen (und zwar durch Aufruf der Funktion Swap in Zeile 25). Die Funktion Swap in den Zeilen 1 bis 6 übernimmt einen Zeiger auf das Array sowie die aktuelle Position und vertauscht dann die beiden Zahlen an dieser Position.

Nach dem Sortieren wird der Inhalt des Arrays erneut im Ausgabefenster durch die TRACE-Anweisungen in den Zeilen 31 bis 33 angezeigt.

Die Trace-Ausgabe des Programms erscheint im Ausgabefenster des Visual Studios, wie es Tabelle E.3 angibt.

Tabelle E.3: Ausgabe des Sortierprogramms

Vor dem Sortieren

Nach dem Sortieren

[1] = 42

[1] = 1

[2] = 68

[2] = 25

[3] = 35

[3] = 35

[4] = 1

[4] = 42

[5] = 70

[5] = 59

[6] = 25

[6] = 63

[7] = 79

[7] = 65

[8] = 59

[8] = 68

[9] = 63

[9] = 70

[10] = 65

[10] = 79

Die Makros ASSERT und VERIFY

Mit dem Makro ASSERT kann man sich vergewissern, ob Bedingungen TRUE sind. Als Parameter übergibt man an ASSERT einen Ausdruck, der entweder TRUE oder FALSE liefert. Wenn der Ausdruck gleich TRUE ist, läuft alles bestens. Liefert der Ausdruck FALSE , stoppt das Programm, und es erscheint das Dialogfeld Debug Assertion Failed! (siehe Abbildung E.7). Das Dialogfeld bietet die Optionen Beenden des Programms, Wiederholen des Codes oder Ignorieren der Annahme. Außerdem zeigt es das Programm, die Quellcodedatei und Zeilennummer an, wo die Annahme (Assertion) fehlerhaft war. Wenn Sie Beenden wählen, wird die Sitzung beendet. Wiederholen ist die wahrscheinlich nützlichste Option, weil der Compiler dann den Code zeigt, wo das Makro ASSERT gescheitert ist und Sie die Ursache herausfinden können. Wenn Sie bereits die Annahme kennen oder sich nicht darum kümmern möchten, wählen Sie Ignorieren und setzen das Programm fort, was dann allerdings zu einem schlimmeren Fehler führen kann.

Abbildung E.7:
Das Dialogfeld Debug Assertion Failed! hilft beim Aufspüren von Bugs

Mit ASSERT prüft man oftmals, ob Eingabeparameter an Funktionen korrekt sind. Beispielsweise können Sie die (in Listing E.1 dargestellte) Funktion Swap robuster gestalten, indem Sie deren Eingabeparameter überprüfen. Fügen Sie dazu ASSERT-Makros am Beginn der Funktion Swap wie folgt hinzu:

ASSERT(pdwNumbers);
ASSERT(i>=0 && i<10);

Damit ist sichergestellt, daß der Zeiger auf das Zahlenarray ungleich null ist und daß die Position zum Vertauschen zwischen 0 und 9 liegt. Wenn eine dieser Bedingungen nicht korrekt ist, wird das Dialogfeld Debug Assertion Failed! angezeigt. Mit dieser Technik lassen sich Fehler aufspüren, die durch die Übergabe falscher Parameter an Funktionen entstehen. Es empfiehlt sich, mit dem Makro ASSERT zu prüfen, ob die übergebenen Werte an die einzelnen Funktionen mit den eigenen Erwartungen übereinstimmen.

Ein anderes Makro, ASSERT_VALID, kann man bei von CObject abgeleiteten Klassen - wie bei den meisten MFC-Klassen - verwenden. Dieses Makro führt einen umfangreichen Test des Objekts und seines Inhalts aus, um sicherzustellen, daß das gesamte Objekt in einem korrekten und gültigen Zustand ist. Auf das zu überprüfende Objekt übergibt man einen Zeiger wie im folgenden Beispiel:

ASSERT_VALID(pdwNumbers);

Das Makro ASSERT_KINDOF prüft bei von CObject abgeleiteten Klassen, ob der Klassentyp korrekt ist. Beispielsweise kann man testen, ob ein Zeiger auf das Ansichtsobjekt von der richtigen Ansichtsklasse ist:

ASSERT_KINDOF(CYourSpecialView, pYView);

Das Dialogfeld Debug Assertion Failed! erscheint, wenn das Objekt nicht vom korrekten Klassentyp oder aller davon abgeleiteten Typen ist.

Achten Sie darauf, keinerlei Code, der für die normale Programmausführung erforderlich ist, in ASSERT-Makros unterzubringen, da sie in der Release-Version ausgeschlossen werden. Eine allgemeine Quelle für Fehler bei Anwendungen im Release-Modus, die man schwer auffinden kann, ist etwa folgender Code:

int a = 0;
ASSERT(++a > 0);
if (a > 0) MyFunc();

Im Debug-Modus inkrementiert dieser Code die Integer-Zahl a in der ASSERT-Zeile und ruft dann in der folgenden Zeile die Funktion MyFunc auf, weil a größer als 0 ist. Angenommen, Ihr Verkaufsteam ist darauf versessen, Ihre neue Anwendung vorzuführen. Da es im Debug-Modus keinerlei Probleme gegeben hat, gehen Sie davon aus, daß alles in Ordnung ist. Also kompilieren Sie die Anwendung ruhigen Gewissens im Release-Modus und geben sie an die Verkaufsabteilung weiter. Diese zeigt die Anwendung dem Kunden, und die Anwendung stürzt ab! Alles nur deshalb, weil das ++a nicht ausgeführt wird - der Release-Modus schließt die ASSERT-Zeilen aus.

Das Makro VERIFY unterstützt Sie bei diesem Problem. VERIFY funktioniert wie ASSERT und bringt im Debug-Modus das gleiche Dialogfeld Debug Assertion Failed! auf den Bildschirm, wenn der Ausdruck 0 ist. Im Release-Modus wird der Ausdruck allerdings trotzdem ausgeführt, wobei aber das Ergebnis 0 nicht zur Anzeige des Dialogfelds Debug Assertion Failed! führt. Vielleicht neigen Sie nun dazu, VERIFY immer zu verwenden, wenn Sie einen Ausdruck auswerten, und ASSERT, wenn Sie nur beim Debuggen eine Überprüfung vornehmen wollen. Wenn Sie im vorherigen Beispiel ASSERT durch VERIFY ersetzen, läuft der Code auch im Release-Modus ordnungsgemäß:

VERIFY(++a > 0);

Höchstwahrscheinlich setzen Sie aber VERIFY ein, um Rückgabecodes aus Funktionen zu prüfen:

VERIFY(MyFunc() != FALSE);

Haltepunkte und Einzelschrittbetrieb

Ein Großteil der Probleme läßt sich wahrscheinlich am effektivsten mit den Debugging-Werkzeugen für den Einzelschrittbetrieb und das Setzen von Haltepunkten lösen. Die Unterstützung für verschiedene Typen von Haltepunkten und die verfügbaren Informationen im Einzelschrittbetrieb sind in Visual C++ sehr ausgeklügelt. Hier können wir nur eine Vorstellung davon vermitteln, wie leistungsfähig dieses Werkzeug zur Fehlersuche ist.

Den Schlüssel für den Einzelschrittbetrieb bilden die Haltepunkte. Man kann einen Haltepunkt an jeder beliebigen Stelle im Code setzen und dann das Programm über den Debugger starten. Ist der Haltepunkt erreicht, erscheint der Code im Editorfenster an der Position des Haltepunkts. Ab hier können Sie die Programmausführung im Einzelschrittbetrieb oder im normalen Ausführungsmodus fortsetzen.

Um einen Haltepunkt zu setzen, markieren Sie die betreffende Codezeile (indem Sie mit dem Bearbeitungscursor im Editorfenster in dieser Zeile klicken) und klicken dann auf das Symbol Haltepunkt in der Minileiste erstellen (siehe Abbildung E.8) oder drücken (F9). Noch komfortabler lassen sich Haltepunkte setzen oder entfernen, indem Sie im Menü Bearbeiten den Befehl Haltepunkte wählen, um das Dialogfeld Haltepunkte zu öffnen (siehe Abbildung E.9). Ein gesetzter Haltepunkt ist durch einen roten Kreis neben der spezifizierten Zeile gekennzeichnet. Haltepunkte kann man nur für gültige Codezeilen festlegen, so daß Visual Studio manchmal einen von Ihnen gesetzten Haltepunkt automatisch auf die am nächsten liegende gültige Codezeile verschiebt.

Abbildung E.8:
Einen Haltepunkt fügen Sie in den Code über die Minileiste erstellen oder durch Drücken von (F9) ein.

Abbildung E.9:
Einen Haltepunkt über das Dialogfeld Haltepunkte hinzufügen.

Haltepunkte schaltet man ein oder aus, indem man auf das Symbol Haltepunkt (mit dem Bild einer Hand) klickt. Haltepunkte lassen sich auch über das Dialogfeld Haltepunkte entfernen. Dafür stehen die Schaltflächen Entfernen und Alle entf.(ernen) zur Verfügung. Haltepunkte kann man auch beibehalten, aber deaktivieren. Dazu schaltet man im Dialogfeld Haltepunkte das Kontrollkästchen neben dem im Listenfeld aufgeführten Haltepunkt aus. Ein erneutes Klicken auf das Kontrollkästchen schaltet es ein und aktiviert den Haltepunkt wieder.

Nachdem Sie den oder die Haltepunkt(e) gesetzt haben, können Sie den Code über den Debugger mit Erstellen / Debug starten / Ausführen ablaufen lassen. Alternativ klicken Sie auf das Symbol Ausführen (links neben dem Symbol Haltepunkt auf der Minileiste erstellen - siehe Abbildung E.8) oder drücken die Taste (F5).

Das Programm läuft dann normal, bis es den Haltepunkt erreicht. Hier stoppt die Programmausführung, und ein Pfeil zeigt auf die Zeile mit dem Haltepunkt. Jetzt können Sie über die Symbolleiste Debug im Einzelschrittbetrieb weiterarbeiten, wie es Abbildung E.10 zeigt.

Abbildung E.10:
Der Debugger hat am Haltepunkt gestoppt und erlaubt die Ausführung des Programms im Einzelschrittbetrieb über die Symbolleiste Debug.

Wenn der Debugger gestoppt hat, läßt sich der Inhalt der meisten Variablen anzeigen, indem man einfach den Cursor über die Variable im Editorfenster führt. Der Inhalt der Variablen erscheint dann in einer Art QuickInfo an der Cursorposition. Nähere Angaben zum Inhalt kann man erhalten, wenn man die Variable in das Überwachungsfenster zieht, wie es der nächste Abschnitt erläutert.

Um den Code in Einzelschritten zu durchlaufen, verwenden Sie die vier Schaltflächen mit den geschweiften Klammern auf der Symbolleiste Debug oder wählen im Menü Debug einen der Schrittbefehle aus. Die verfügbaren Schrittoptionen zeigt Tabelle E.4. Die Befehle sind sowohl im Menü Debug als auch auf der Symbolleiste Debug enthalten.

Tabelle E.4: Im Einzelschrittbetrieb verfügbare Schrittoptionen

Symbol

Schrittoption

Zugriffstaste

Wirkung

In Aufruf springen

F11

Der Debugger führt die aktuelle Zeile aus. Befindet sich der Cursor über einem Funktionsaufruf, tritt der Debugger in die Funktion ein.

Aufruf als ein Schritt

F10

Wie bei »In Aufruf springen«. Befindet sich jedoch der Cursor über einem Funktionsaufruf, führt der Debugger die Funktion mit normaler Geschwindigkeit aus und stoppt bei Rückkehr aus der Funktion. Man führt die Funktion praktisch in einem Schritt aus.

Ausführen bis Rücksprung

Umschalt+F11

Der Debugger führt die restlichen Codezeilen der Funktion mit normaler Geschwindigkeit aus und stoppt bei der Rückkehr in die aufrufende Funktion.

Ausführen bis Cursor

Strg+F10

Der Debugger führt das Programm normal aus, bis die festgelegte Cursorposition erreicht ist. Um die Position festzulegen, klicken Sie auf die Zeile, bis zu der der Debugger das Programm ausführen soll.

Ausführen

F5

Setzt die Programmausführung mit normaler Geschwindigkeit fort, bis der Debugger den nächsten Haltepunkt erreicht.

Debug beenden

Umschalt+F5

Stoppt den Debugger und kehrt in den Bearbeitungsmodus zurück.

Erneut starten

Strg+Umschalt+F5

Startet das Programm erneut von Anfang an und stoppt auf der allerersten Codezeile.

Ausführung anhalten

Hält ein bei normaler Geschwindigkeit ablaufendes Programm an.

Code-Änderungen zuweisen

Alt+F10

Diese Option erlaubt es, den Code zu kompilieren, nachdem man Änderungen während einer Debug-Sitzung durchgeführt hat. Anschließend kann man das Debuggen von der letzten Position aus wiederaufnehmen.

Mit diesen Befehlen können Sie den Programmablauf überwachen und den Inhalt der Variablen verfolgen, wie sie der Code manipuliert. Der gelbe Pfeil im Editorfenster zeigt immer auf die als nächstes auszuführende Anweisung.

Der nächste Abschnitt beschäftigt sich näher mit den Debug-Fenstern, die Sie bei angehaltenem Debugger verwenden können.

Bearbeiten und Fortsetzen

Ein wesentliches neues Merkmal von Visual C++ 6 ist die Fähigkeit, den Code zu bearbeiten und dann fortzusetzen. Das bedeutet, daß man den Code ändern oder bearbeiten kann, während der Debugger angehalten hat. Nach der Bearbeitung wird der Befehl Code-Änderungen zuweisen im Menü Debug (sowie das entsprechende Symbol auf der Symbolleiste Debug) aktiviert. Sie können dann den Befehl (oder die entsprechende Schaltfläche) Code-Änderungen zuweisen wählen, um die vorgenommenen Änderungen am Code zu kompilieren und dann das Debuggen mit dem geänderten Code fortzusetzen. Auf diese Weise lassen sich während des Debuggens Programmfehler beseitigen, wobei man mit der Fehlersuche an der gleichen Stelle des Codes und vor allem mit den gleichen Variableneinstellungen fortsetzen kann. Insbesondere beim Debuggen größerer und komplexer Programme kommen die Vorteile dieses neuen Funktionsmerkmals voll zur Geltung.

Programmvariablen überwachen

Abbildung E.11 zeigt die Debug-Fenster Überwachung und Variablen. Die Fenster zeigen den Inhalt von Variablen an, wenn der Debugger gestoppt hat. Um die Fenster einzusehen, wählen Sie aus dem Menü Ansicht den Befehl Debug-Fenster und anschließend den jeweiligen Eintrag des aufklappenden Untermenüs oder klicken auf die Symbole der Symbolleiste.

Abbildung E.11:
Das Überwachungsfenster zeigt den Inhalt von Variablen während des Debuggens an.

Das Variablenfenster zeigt immer die lokalen Variablen der im Kombinationsfeld Kontext am oberen Rand des Fensters aufgeführten Funktion an. Um zur aktuellen Funktion zu gelangen, können Sie die Liste des Kombinationsfelds öffnen. Es erscheinen dann alle Funktionen, die nacheinander aufgerufen wurden. Es handelt sich hierbei um den sogenannten Aufruf-Stack. Er zeigt den aktuellen Kontext innerhalb des Programms mit einer Liste von Funktionen, die aufgerufen wurden, um zur momentan ausgeführten Funktion des Programms zu gelangen, wo der Debugger gestoppt hat. Wenn Sie eine andere Funktion wählen, werden die relevanten lokalen Variablen für diese Funktionsebene angezeigt.

Die Objektzeiger kann man erweitern, indem man auf das Plussymbol neben dem Zeigernamen klickt. Der spezielle C++-Zeiger this wird immer für Elementfunktionen von Klassen angezeigt und läßt sich öffnen, um alle Elementvariablen für das aktuelle Objekt anzuzeigen.

In das Überwachungsfenster kann man Variablennamen über die Tastatur eintragen oder Variablen aus dem Editorfenster ziehen (nachdem man sie mit dem Mauszeiger markiert und invertiert hat). Die in den angezeigten Variablen gespeicherten Werte sind solange zu sehen, bis die Variablen den Gültigkeitsbereich verlassen (das heißt, nicht mehr für die momentan untersuchte Funktion relevant sind).

Es lassen sich auch einfache Typumwandlungen und Array-Indizes im Überwachungsfenster eingeben, um die zugehörigen Werte anzuzeigen. Klicken mit der rechten Maustaste schaltet die angezeigten numerischen Werte zwischen hexadezimaler und dezimaler Darstellung um. Bei der schrittweisen Untersuchung des Programms werden die im Überwachungsfenster angezeigten Variablen jeweils aktualisiert, so daß man verfolgen kann, wie das Programm die Variablen ändert.

Andere Fenster des Debuggers

Weitere Fenster des Debuggers sind über Ansicht / Debug-Fenster , das Menü Debug bzw. über die Symbole auf der Symbolleiste Debug erreichbar. Zu diesen Fenstern gehören:

KERNEL32! bff88f75()

Zusätzliche Debugging-Tools

Neben den integrierten Debugger-Werkzeugen gibt es verschiedene eigenständige und nützliche Hilfsprogramme. Diese lassen sich über das Menü Extras und den jeweiligen Menübefehl starten.

Die zusätzlichen Programme dienen vor allem dazu, spezielle Elemente des Betriebssystems wie Windows-Nachrichten, laufende Prozesse und registrierte OLE-Objekte zu verfolgen, um die verfügbaren Informationen beim Debuggen einer Anwendung zu erweitern.

Spy++

Spy++ gehört zweifellos zu den nützlichsten dieser Werkzeuge. Mit Spy++ kann man die hierarchischen Beziehungen von übergeordneten zu untergeordneten Fenstern, die Position und Flageinstellungen für Fenster und die Basisklassen der Fenster sehen. Außerdem lassen sich die Nachrichten überwachen, die Windows an ein Fenster schickt.

Beim Start von Spy++ erscheinen alle Fenster auf dem aktuellen Desktop, deren gleichgeordnete Fenster und die Basisklassen der Fenster für jedes Objekt (siehe Abbildung E.12). In der in Abbildung E.12 dargestellten Ansicht wurde ein Bildlauf nach unten bis zur CD-Wiedergabe von Microsoft Windows durchgeführt. Spy++ zeigt alle Schaltflächen und Kombinationsfelder, die wiederum selbständige Fenster als untergeordnete Fenster zum Hauptfenster der CD-Wiedergabe sind.

Abbildung E.12:
Das Fenster von Spy++ mit dem Abschnitt CD-Wiedergabe des Windows-Desktop.

Das Menü Spy enthält folgende Befehle:

Spy++ läßt sich aufgrund der umfangreichen Funktionen an dieser Stelle nicht erschöpfend behandeln. Das Programm ist aber als Werkzeug unübertroffen, wenn Sie sich näher mit der Struktur der Hierarchie und der Nachrichtenverarbeitung unter Windows beschäftigen möchten. Mit Spy++ können Sie eine Menge Kenntnisse sammeln, indem Sie sich einfach einmal kommerzielle Anwendungen ansehen. Spy++ ist auch ein hervorragendes Hilfsmittel, um Probleme in der eigenen Anwendung mit der Nachrichtenverarbeitung aufzuspüren, um sich zu vergewissern, daß die Fenster die korrekten Nachrichten erhalten, und um zu sehen, wie diese Nachrichten aufeinanderfolgen.

Prozess-Viewer

Mit der Anwendung Prozess-Viewer (PView95.exe) lassen sich alle Prozesse detaillierter als mit Spy++ anzeigen. Um das Programm zu starten, wählen Sie Start / Programme / Microsoft Visual Studio 6.0 / Microsoft Visual Studio 6.0-Dienstprogramme / Prozess-Viewer. Die Anwendung listet die Prozesse auf, die auf Ihrem Computer laufen. Um die Liste zu sortieren, klicken Sie auf den jeweiligen Spaltenkopf. Markieren Sie dann einen Prozeß, um alle damit verbundenen Threads anzuzeigen. Abbildung E.15 zeigt die Prozeßanzeige, die mit der ausgewählten Anwendung Developer Studio (MSDEV.EXE) läuft und die Vielzahl der zugehörigen Threads anzeigt.

Abbildung E.15:
Prozeßanzeige zeigt MSDEV.EXE und dessen Threads.

Der OLE/COM-Objektkatalog

Das Dienstprogramm OLE/COM-Objektkatalog zeigt alle in Ihrem System registrierten OLE/COM-Objekte einschließlich der ActiveX-Steuerelemente, Typenbibliotheken, eingebetteten Objekte, Automatisierungsobjekte und vieler anderer Kategorien.

Man kann sogar Instanzen der verschiedenen Objekte erzeugen und deren Schnittstellen im Detail ansehen. Der OLE/COM-Objektkatalog ist hilfreich, wenn man OLE/ COM-Anwendungen entwickelt oder nach einem undefinierten ActiveX-Steuerelement sucht.

Die MFC-Ablaufverfolgung (MFC Tracer)

Mit dem Dienstprogramm MFC-Ablaufverfolgung (siehe Abbildung E.16) kann man die normale Verfolgung stoppen oder spezielle Ablaufverfolgungen in die Protokollierung aufnehmen. Wenn Sie dieses Werkzeug wählen, erscheint eine Gruppe von Kontrollkästchen, über die Sie die Optionen zur Ablaufverfolgung einstellen können.

Abbildung E.16:
Die Optionen der MFC-Ablaufverfolgung

Es lassen sich Windows-Nachrichten, Datenbanknachrichten, OLE-Nachrichten und viele andere Stufen der Ablaufverfolgung hinzufügen, um ausgewählten Problemen auf die Spur zu kommen. Diese Nachrichten werden dann durch den MFC-Code für die verschiedenen ausgewählten Flags generiert.

Sie können sogar die normale Ablaufverfolgung, die von Ihrer Anwendung generiert wird, unterdrücken, wenn Sie das Kontrollkästchen Ablaufverfolgung aktivieren ausschalten.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1