Überraschende Wendung
Überraschende Wendung
Trotz peinlichster Beseitigung auch des kleinsten Compiler-Hinweises funktioniert der eine oder andere Code nicht immer wie erwartet. So enthält folgendes Programm ein subtiles Problem, welches es zu finden gilt. Versuchen Sie zunächst, anhand einer Analyse des Codes die vermutliche Ausgabe des folgenden Programmes vorherzusagen. Prüfen Sie sodann, ob die Prognose mit dem tatsächlichen Ergebnis übereinstimmt.
Programmcode
program Suprise; (*$APPTYPE CONSOLE*) uses SysUtils; type TTestFunc = function( i : Integer) : Boolean; function IsEven( i : Integer) : Boolean; begin result := not Odd(i); end; function TestAndFormat( i : Integer; func : TTestFunc) : string; begin if func(i) then result := Format( '%d ', [i]); end; var i : Integer; begin write( 'Some even numbers: '); for i := 1 to 10 do Write( TestAndFormat(i, IsEven)); writeln; writeln; write('Hit ENTER to close ... '); readln; end.
Die Auflösung folgt weiter unten.
Weiterscrollen ...
Weiterscrollen ...
Auflösung
Hätten Sie diese Ausgabe erwartet?
Lange Strings sind in Delphi, genau wie auch dynamische Arrays, Variants und Interfacezeiger, mit einer eingebauten Intelligenz behaftete Datentypen. Der generierte CPU-Code pflegt bekanntlich für solche Variablen eine interne Buchführung über die vom Code gehaltenen Referenzen. Das dient der korrekten Verwaltung des Speichermanagementes und wird auch für die Copy-On-Write-Semantik benötigt. Aus diesem Grund werden lokale Variablen und Recordfelder dieser Datentypen grundsätzlich immer durch entsprechend generierten CPU-Code automatisch initialisiert. Man würde daher zu recht erwarten (und der Compiler bemängelt diese Annahme ja auch nicht), daß die Funktion TestAndFormat bei nicht zutreffender Prüfbedingung einen Leerstring als Ergebnis liefert.
Leider ist genau das ein Irrtum. Die als Rückgabewert dienende String-Variable gehört nämlich nicht zu TestAndFormat, sondern wird vielmehr im Scope der aufrufenden Funktion als eine zusätzliche, unsichtbare Variable per Compilermagie verwaltet. Das aber führt konsequent dazu, daß im Else-Fall von TestAndFormat einfach der aktuelle Wert erhalten bleibt. Führt man sich vor Augen, daß der Rückgabewert einer jeden Funktion technisch nichts anderes als ein weiterer VAR-Parameter ist und würde die Funktion also wie folgt umschreiben, wäre sofort klar, warum das erhaltene Ergebnis zustande kommt:
procedure TestAndFormat( i : Integer; func : TTestFunc; var result : string); begin if func(i) then result := Format( '%d ', [i]); end;
Problematisch ist daran eigentlich nur, daß der Compiler aufgrund der Sachlage eigentlich bemängeln sollte, daß der Rückgabewert der (ursprünglichen) Funktion nicht in allen Fällen explizit gesetzt wird. Problematisch, weil man den Fehler in größeren Routinen schnell übersieht, weil das Verhalten selbst für erfahrene Entwickler intuitiv nicht unmittelbar erwartet wird und last not least wegen seiner Datenabhängigkeit zur Klasse der besonders fiesen Fehler gehört. Es besteht daher eine realistische Chance, daß der Bug trotz ausführlichster Unit-Tests etc. möglicherweise bis in die Lieferung gelangt.