EXIT im Try-Finally

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
icho2099
Beiträge: 22
Registriert: Fr 21. Feb 2020, 19:17
OS, Lazarus, FPC: Win10/64
CPU-Target: 64 Bit
Wohnort: Osterholz-Scharmbeck

EXIT im Try-Finally

Beitrag von icho2099 »

Nur zur Info, falls jemand sich so verrennt wie ich:

Ein Exit im Try-Block einer Try-Finally Konstruktion wird erst NACH den Anweisungen im Finally ausgeführt.
Klar, Exit wenn möglich nicht verwenden.
Aber wenn doch, dann ist es gut zu wissen, dass der Finally Block selbst bei Exit immer noch ausgeführt wird.

Code: Alles auswählen

Try
  myObj := tMyObj.Create()
  myObj.DoThis;
  If MyObj.Problem then begin
    MyObj.Free;			//man soll ja hinter sich aufräumen
    Exit();			//und dann raus hier, dachte ich ...
  end;
Finally
  MyObj.Free;			//aber dieses Free wird immer auch ausgeführt und liefert ein SigSegv
end;
ist hier auch beschrieben

Benutzeravatar
theo
Beiträge: 10499
Registriert: Mo 11. Sep 2006, 19:01

Re: EXIT im Try-Finally

Beitrag von theo »

Dazu ist es da.
Als Link auf die Beschreibung würde ich aber nicht ein Zitat in einem Mailing-List Kommentar angeben, sondern einfach die Doku im Original:
https://www.freepascal.org/docs-html/cu ... se120.html

Warf
Beiträge: 1912
Registriert: Di 23. Sep 2014, 17:46
OS, Lazarus, FPC: Win10 | Linux
CPU-Target: x86_64

Re: EXIT im Try-Finally

Beitrag von Warf »

Das ist die komplette Idee von Finally. Try-Finally wird oftmals mit Exception handling gleich gesetzt, aber das ist eigentlich ein Fehlverständnis. Ein Finally wird immer ausgeführt, egal ob der Block durch Exit, Exception, Continue, Break oder durch normales durchlaufen verlassen wird. Daher ist Try-Finally immer nützlich, selbst wenn man mit 100% Exceptionfreiem Code arbeitet.

Beispiel, suche eine Datei mit einem bestimmten format und inhalt:

Code: Alles auswählen

function FindFile(files: TStringList; SearchPattern: String): String;
var
  fl: String;
  fs: TFileStream;
begin
  Result := '';
  for fl in files do
  begin
    fs := TFileStream.Create(fl);
    try
      if fs.ReadDWord <> MAGIC_NUMBER then
        continue; // Finally wird aufgerufen und FS freigegeben
      if CheckContent(fs, SearchPattern) then
        Exit(fs); // Finally wird Aufgerufen und FS Freigegeben
    finally
      fs.Free;
    end;
  end;
end;
Ohne Finally müsste bei dem Continue und bei dem Exit sowie am ende des loops ein fs.Free stehen. Wenn man eine neues Such oder Ausschlusskriterium hinzufügt mit einem neuen Continue, Break oder Exit, muss man wieder dran denken fs Freizugeben. Mit Try-Finally ist das ganze viel einfacher. Immer wenn man was erstellt macht man einen Try-Finally block auf, und egal was man darin macht und wie er verlassen wird, am ende wird immer das Finally aufgerufen.

Das ist ein riesen vorteil gegenüber z.B. C wo man bei jeder break condition aufräumen muss, was dann oftmals zu solchem code führt (echter Production code von GNU cat):

Code: Alles auswählen

if (fstat (input_desc, &stat_buf) < 0)
{
  error (0, errno, "%s", quotef (infile));
  ok = false;
  goto contin;
}

/* Optimal size of i/o operations of input.  */
idx_t insize = io_blksize (stat_buf);

fdadvise (input_desc, 0, 0, FADVISE_SEQUENTIAL);

/* Don't copy a nonempty regular file to itself, as that would
merely exhaust the output device.  It's better to catch this
error earlier rather than later.  */

if (out_isreg
  && stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino
  && lseek (input_desc, 0, SEEK_CUR) < stat_buf.st_size)
{
  error (0, 0, _("%s: input file is output file"), quotef (infile));
  ok = false;
  goto contin;
}
Das ist tatsächlich recht ähnlich zu dem Beispiel oben durch eine Liste an dateien iteriert, die geöffnet werden und dann am ende geschlossen werden müssen, doch statt einfach ein try-finally mit continue zu haben, muss hier goto zu einem registrierten contin label gemacht werden, was dann praktisch als finally block fungiert. Im vergleich dazu hat man es mit try-finally in Pascal echt einfach

PS: Und falls jemand wie ich sich fragt was mit beliebigen Jumps (also non break, continue, exit und raise) ist, try-finally verbietet Goto über try grenzen hinweg:

Code: Alles auswählen

label
  afterFinally;
begin
  try
    goto afterFinally; // error
  finally
  end;
  afterFinally:
  WriteLn('After');
end. 
Der einzige weg Try-Finally zu umgehen sind also ASM Jumps, oder SetJmp LongJmp (was eh den Exception state zerschießt und damit eh in den allermeisten Fällen zu Fehlern führt), aber wenn man so tief runter geht ist eh alles was man macht auf eigene Gefahr.

Antworten