Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Wie Try-Blöcke richtig setzen?
tommie-lie - Mi 16.04.03 16:45
Titel: Wie Try-Blöcke richtig setzen?
Nachdem mich nun diese Frage einige Zeit beschäftigt hat und ich in diversen Sourcecodes mindestens zwei verschiedene Lösungen gefunden habe, habe ich eine kleine Frage zu Exceptions.
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| var Stream: TFileStream; Buffer: PChar; begin Stream := TFileStream.Create('foo.txt', fmCreate); GetMem(Buffer, 123); Stream.Read(Buffer^, 123); FreeMem(Buffer); Stream.Free; end; |
Ungeachtet dessen, daß ich eine leere Datei erstelle und aus ihr lese, kann in diesem Codeschnipsel jede Funktion eine Exception auslösen, angefangen mit TFileStream.Create. Diese löst eine Exception aus, wenn die Datei nicht erzeugt werden kann. GetMem löst eine exception aus, wenn nicht genug Speicher vorhanden ist. Die Expcetions möchte ich aber abfangen, und dazu scheitn es wohl von mehreren Leuten mehrere Lösungsvorschläge zu geben.
Lösung 1:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| Stream := TFileStream.Create; try GetMem(Buffer, 123) try Stream.Read(Buffer^, 123); finally FreeMem(Buffer); //Speicher auf Freigeben, komme was wolle end; finally Stream.Free; //Stream wieder freigeben, komme was wolle end; |
Lösung 2:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11:
| try Stream := TFileStream.Create('foo.txt', fmCreate); try GetMem(Buffer, 123); Stream.Read(Buffer^, 123); finally FreeMem(Buffer); end; finally Stream.Free; end; |
Der Unterschied liegt im Detail. Aber was ist richtig?
Ich für mich würde sagen, zweitere Lösung ist besser, denn dort steht das FileStream.Create im Try-Block, geht das schief wird also auf jeden Fall wieder Stream.Free aufgerufen. Allerdings sieht es bei den Speicherfunktionen anders aus. FreeMem erzeugt einen Runtime error, wenn Buffer nicht auf einen Speicherbereich zeigt. Ist GetMem also schon gecrasht, verweist Buffer nicht auf Speicher und deswegen würde FreeMem einen error erzeugen, den ich aber nicht abfangen kann.
Obwohl mir die zweite Lösung für sinnvoller erscheint, findet man immer wieder Code der ersten Form. In den Beispielen der Online-Hilfe finde ich sogar nur Code der ersten Form, weswegen ich leicht verwirrt bin, warum erstere von Borland benutzt wird.
Kann mich einer aufklären?
Popov - Mi 16.04.03 17:18
Unabhängig des Problems - Create braucht man nicht schützen.
tommie-lie - Mi 16.04.03 17:33
Nie?
Und warum heißt es dann in der OH, daß das Create einiger Klassen Exceptions auslöst? Wenn man die nicht schützen muss, interessiert es mich als Programmeirer doch im Prinzip gar nicht...
tommie-lie - Fr 18.04.03 17:26
Mittlerweile habe ich mehrere Instanzen von verschiedenen Klassen erzeugt, wieder freigegeben, absichtlich Fehler verursacht und in Kleinarbeit zu folgendem Ergebnis gekommen:
Entgegen Popovs Behauptung müssen auch Create-Aufrufe durch try gechützt werden, damit keine Fhlermeldungen im fertigen Kompilat erscheinen. Da bei einem fehlgeschlagenen Createn der Klasse die entsprechende Variable wertlos ist, würde wiedrum ein Free eine Zugriffsverletzung erzeugen. Vollkommen richtig und absolut "stumm" fängt man Exceptions von Filestreams daher folgendermaßen ab:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| FS := nil; // das ist der Trick try // Öffnen eines nicht-existenten Files -> Fehlermeldung im Kompilat FS := TFileStream.Create('C:\nichda.file', fmOpenRead); except // eigene Fehlermeldung, aufgerufen wenn Datei nicht geöffnet werden kann // ShowMessage('File Open Error'); end; FS.Free; // dank nil erzeugt das keine Access Violation |
Und für die dynamischen Speicherfunktionen:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| Buffer := nil; // auch hier darf die Variable nicht ungültig sein, also erstmal nil try // wer hat schon 9 TB Speicher? GetMem(Buffer, 9999999999); except // eigene Fehlermeldung, aufgerufen wenn Speicher nicht alloziiert werden kann // ShowMessage('File Open Error'); end; FreeMem(Buffer); // dank nil erzeugt das keine "Invalid Pointer Operation" |
Zu beachten ist, daß man weiterhin die Zugriffe einzeln sichern muss. try-except/finally-Blöcke kann man beliebig Schachteln, somit kann man für jeden Befehl eine andere Fehlermeldung ausgeben, die ohne Sicherung evtl nur eine platte "Access Violation" erzeugt hätte. Oben genannte Beispiele sind also nur der äußerste Bereich, der das Erzeugen der entsprechenden Speicherbereiche sichert, das eigentliche Arbeiten kann (bei Bedarf) wieder extra geichert werden, oder auch in den gleichen try-Block wie das Create/GetMem stehen (letzteres macht auch keine Probleme).
Die Zweite Lösung ist also genaugenommen richtiger, aber Free würde im Finally trotzdem eine Zugriffsverletzung erzeugen, ebenso wie ein FreeMem mit einem ungültigen Pointer (nil ist gültig, also keine Probleme). Inwiefern jetzt die erste Variante noch von Systemabstürzen gefährdet ist, konnte ich nicht feststellen. Bei mir ist gar nichts heftig abgestürzt, es kamen nur jede Menge Fehlermeldungen.
Die Codebeispiele wie sie in der Onlinehilfe stehen, sind also genaugenommen auch nicht hundert prozentig richtig, zumindest fangen sie den Fehler nicht vollständig ab, sondern vermeiden höchstens einen Absturz, wenn auch noch versucht wird auf den ungültigen Speicherbereich zuzugreifen.
Delete - Fr 18.04.03 19:16
| tommie-lie hat folgendes geschrieben: |
Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9:
| FS := nil; // das ist der Trick try // Öffnen eines nicht-existenten Files -> Fehlermeldung im Kompilat FS := TFileStream.Create('C:\nichda.file', fmOpenRead); except // eigene Fehlermeldung, aufgerufen wenn Datei nicht geöffnet werden kann // ShowMessage('File Open Error'); end; FS.Free; // dank nil erzeugt das keine Access Violation |
|
try-finally-end heißen auch Ressourcenschutzblöcke und dienen dazu belegte Ressourcen auch im Fehlerfall wieder frei zu geben. Da ein fehlgeschlagenes creete keine Ressourcen belegt, braucht es auch nicht geschützt zu werden.
try-finally-end sollte nicht dazu benutzt werden schlampig programmierte Routinen zum Laufen zu bringen oder am Laufen zu halten. Erstmal zeugt das von schlechten Programmierstil, davon das der Programmierer keine Ahnung hat, was er da macht und außer dem kostet jede Exception hunderte von Taktzyklen.
Und so sieht dein Beispiel-Code vernünftig programmiert aus:
Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| if FileExists('c:\vorhandenedatei.txt') then begin FS := TFileStream.Create('c:\vorhandenedatei.txt', fmOpenRead); try // was mit machen finally FS.free; end; end else begin ShowMessage('Datei nicht gefunden.'); end; |
tommie-lie - Fr 18.04.03 22:48
| Luckie hat folgendes geschrieben: |
try-finally-end heißen auch Ressourcenschutzblöcke und dienen dazu belegte Ressourcen auch im Fehlerfall wieder frei zu geben.
Da ein fehlgeschlagenes creete keine Ressourcen belegt, braucht es auch nicht geschützt zu werden. |
Stimtm schon, aber eine Fehlermeldung kommt trotzdem, und die sollte auch abgefangen werden.
| Zitat: |
| try-finally-end sollte nicht dazu benutzt werden schlampig programmierte Routinen zum Laufen zu bringen oder am Laufen zu halten. Erstmal zeugt das von schlechten Programmierstil, davon das der Programmierer keine Ahnung hat, was er da macht und außer dem kostet jede Exception hunderte von Taktzyklen. |
Das ist richtig. Ich habe die nicht vorhandene Datei auch nur als einfaches, anschauliches Beispiel gewählt. Ich überprüfe vorher auch
immer mit FileExists die Datei. Genauso gut hätte ich eine Read-Only-Datei nehmen können, die ich mit fmOpenWrite öffne, erzeugt auch Fehler, hätte aber mehr Tipparbeit an Erklärung bedarft... (da kommt der faule Sack in mir durch ;-) )
Also stört euch nicht dran, daß ich eine nicht vorhandee Datei versucht habe, mit Exceptions abzufangen, war nur der Einfachheit halber und sollte das Prinzip möglichst schnell erklären.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2026 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!