Fehlerbehandlung
Die Vorbereitung
Wird ein Programm erfolgreich kompiliert, so werden die symbolischen Namen der Bezeichner entfernt. Erweiterte Compiler-Optimierungen können dazu führen, dass der Programmablauf für uns nicht mehr wirklich nachvollziehbar wird. Darum weisen wir den Compiler an, zusätzliche Debug-Informationen in die ausführbare Datei zu übernehmen und deaktivieren außerdem die Optimierungen. Die erzeugte Datei wird durch die Aufnahme der Debug-Informationen wesentlich größer. Erlangt unser Programm dann irgendwann die Reife, so können wir diese, dann unnötigen Informationen, aus dem fertigen Programm entfernen. Delphi bietet uns hier einen relativ einfachen Mechanismus an, beim Kompilieren zwischen Endversion (Release) und Analyse (Debug), zu unterscheiden. Unter dem Menüpunkt Projekt/Standardoptionen/Delphi wählen wir als Standardprojektoption die Build-Konfiguration Debug aus.
Neu gestartete Projekte werden nun automatisch mit Debug-Informationen kompiliert. Daran erkennen wir auch, dass sich Debug-Informationen auf Projektebene bewegen. Ändern wir Einstellungen eines bestehenden Projekts, jetzt unter Menüpunkt Projekt/Optionen zu finden, so reicht ein Kompilieren des Quelltextes durch Strg+F9 allein nicht, da dabei nur die Änderungen am Quelltext zur letzten Übersetzung berücksichtigt werden. Das Projekt muss also durch die Tastenkombination Umsch+F9 neu erzeugt werden.
Wir nehmen noch einige ergänzende Einstellungen vor:Im Unterpunkt Delphi-Compiler/Compilieren aktivieren wir unter Laufzeitfehler die Bereichs- und Überlaufprüfung.
Und im Unterpunkt Delphi-Compiler/Linken aktivieren wir die Debug-Informationen, damit diese in die ausführbare Datei geschrieben werden.
Ist die Überlaufprüfung aktiviert, so wird bei arithmetischen Integer-Operationen auf ein Über- bzw. Unterschreiten der Grenzen des Datentyps geprüft. Falls das im folgenden Konsolenprogramm so ist, wird eine Fehlermeldung erzeugt und angezeigt. Ist die Prüfung deaktiviert, so zeigt sich das kommentierte Verhalten.
program Ueberlaufpruefung; {$APPTYPE CONSOLE} uses SysUtils; var IntZahl: Integer; begin try IntZahl := High(Integer); //Maximum im Integer-Bereich WriteLn(IntZahl); IntZahl := IntZahl + 1; //Maximum + 1 => Überlauf = Minimum im Integer-Bereich WriteLn(IntZahl); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; ReadLn; end.
Bereiche von Datentypen können natürlich auch unterlaufen werden. Wollen wir ähnlich gelagerte Fälle mit Integer-Typen kleiner 4 Bytes ausprobieren, dann müssen wir allerdings die Bereichsprüfung und nicht die Überlaufprüfung deaktivieren, da alle arithmetischen Operationen intern mittels 32-Bit-Werten ausgeführt werden. Die Berechnung an sich läuft also fehlerfrei, nur die implizite Typumwandlung verletzt hier, beim Zuweisen des Ergebnisses an den kleineren Datentyp, die Bereichsgrenzen.
var ByteZahl: Byte; begin try ByteZahl := Low(Byte); //Minimum im Byte-Bereich WriteLn(ByteZahl); ByteZahl := ByteZahl - 1; //Minimum - 1 => Unterlauf = Maximum im Byte-Bereich WriteLn(ByteZahl); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; ReadLn; end.
Sofern solche Überläufe nicht ausdrücklick erwünscht sind, sollten diese Prüfungen im Debug-Modus immer aktiviert sein. Insbesondere die Bereichsprüfung ist hier sehr nützlich für uns, da sie anzeigt, wenn Zugriffe außerhalb der Grenzen z.B. eines Arrays oder Strings stattfinden.
program Bereichsprüfung; {$APPTYPE CONSOLE} uses SysUtils; procedure ZeigeArrayBereichsverletzung; var intArray: array[0..9] of Integer; i: Integer; begin for i := 1 to 10 do //Nicht Index 0, dafür Index 10 beschreiben intArray[i] := i; for i := 0 to 10 do //Index 0..9 + 10 auslesen WriteLn(intArray[i]); end; begin try ZeigeArrayBereichsverletzung; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; ReadLn; end.
Im gezeigten Beispiel wird, bei deaktivierter Bereichsüberprüfung, über die Grenzen des Arrays hinaus in den Speicher geschrieben und daraus gelesen. Was immer dort im Speicher stand, es wurde überschrieben und die Konsequenzen daraus sind nicht vorhersehbar. Zusätzlich sehen wir an Index 0 des Arrays einen uninitialisierten Wert.