Exception-Handling und Freigeben mehrer Objekte

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
Dee
Beiträge: 54
Registriert: Do 10. Jul 2014, 20:56
OS, Lazarus, FPC: Windows 10 Pro 64-bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: Ryzen 5 2600

Exception-Handling und Freigeben mehrer Objekte

Beitrag von Dee »

Moinsen Leute,

wie handhabt ihr das Exception-Handling und Freigeben mehrer Objekte?

Ich habe folgendes Beispiel, so wie ich es machen würde:

Code: Alles auswählen

procedure TForm1.Button1Click(Sender: TObject);
var
  SL1, SL2: TStringList;
begin
  // SIGSEGV-Fehler verhindern, außerdem gibt es somit keine "herumirrenden" Objekt-Pointer
  SL1 := nil;
  SL2 := nil;

  try
    try
      SL1 := TStringList.Create;
      SL2 := TStringList.Create;

      SL1.AddText('Das ist ein Text, der einer Stringlist hinzugefügt wurde!');
      SL2.AddText('Das ist ein weiterer Text, der einer Stringlist hinzugefügt wurde!');
    finally
      // Bei FreeAndNil (strengenommen eher NilAndFree) kann laut Dokumentaion ein Fehler auftreten, deswegen manuell.
      // Außerdem keine "herumirrenden" Pointer von Objekten, die auf eine ungültige Adresse zeigen.
      SL1.Free;
      SL1 := nil;

      SL2.Free;
      SL2 := nil;
    end;
  except
    on E: Exception do
    begin
      ShowMessageFmt('Exception:' + #13#10 + '%s - %s', [E.QualifiedClassName, E.Message]);
      Exit;  // sinnvoll?
    end;
  end;
  
  // weiterer Code (...)
end;
Mein Ziel dabei ist es, so wenig verschachtelte Try-Except- oder Try-Finally-Blöcke zu benutzen. Aber vielleicht gibt es dabei auch einen Haken.

Mitunter als Anlaufpunkt dienten folgendende Artikel:

Try-Finally Blocks for Protecting Multiple Resources in Delphi: https://blog.marcocantu.com/blog/2018-j ... elphi.html
[SOLVED] Handling Exception: https://forum.lazarus.freepascal.org/in ... ic=24690.0

Dann noch ein paar Fragen, die ich mir gestellt habe:

1. Ist es sinnvoll, erst die Objekte freizugeben und dann auf die Exception zu reagieren oder doch lieber umgekehrt?
2. Sollte nach der Exception der Code weiterlaufen oder lieber mit einem Exit die Prozedur beendet werden?

Bin gespannt auf eure Ideen und Vorschläge.

Grüße
Dee

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von sstvmaster »

Hallo Dee,

ich mache immer so wie hier beschrieben, siehe letzter Abschnitt: https://wiki.freepascal.org/Try/de

So wie im Link beschrieben wird zu erst auf eine Exception reagiert.
Wenn eine Exception vorhanden ist, kann man darauf reagieren, danach wird das Objekt dann im Finally block freigegeben.
Wenn keine Exception vorhanden ist wird das Objekt freigegeben.

Aber ich denke das kommt drauf was man machen will.
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von Socke »

sstvmaster hat geschrieben:
So 7. Feb 2021, 20:37
Aber ich denke das kommt drauf was man machen will.
Wenn du eine EOutOfMemory Exception abfängst, ist es in der Regel vorteilhafter, zuerst deine Objekte freizugeben und damit wieder ein wenig Speicher für die Fehlerbehandlung zu erhalten. Dass dieser Speicher ausreicht, ist aber nicht garantiert.

Wenn du in deine Fehlermeldung noch weitere Daten aus den Objekten integrieren möchtest, musst du die Objekte selbstverständlich zuletzt freigeben.

Beides zusammen erzeugt auch in Pascal sehr unleserlichen Code. Entweder kapselst du die einzelnen Fehlerbehandlungen in einzelne Methoden oder nutzt für globale Fehler (EOutOfMemory, EAccessViolation etc.) einen globalen Handler in der Anwendung (z.B. TApplication.OnException).
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

sstvmaster
Beiträge: 575
Registriert: Sa 22. Okt 2016, 23:12
OS, Lazarus, FPC: W10, L 2.2.6
CPU-Target: 32+64bit
Wohnort: Dresden

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von sstvmaster »

Socke hat geschrieben:
Mo 8. Feb 2021, 08:49
Wenn du eine EOutOfMemory Exception abfängst, ist es in der Regel vorteilhafter, zuerst deine Objekte freizugeben und damit wieder ein wenig Speicher für die Fehlerbehandlung zu erhalten. Dass dieser Speicher ausreicht, ist aber nicht garantiert.
Und woher weiß ich welche Objekte sich wie verhalten? Gibt es da eine Vorschrift/Anleitung? Ich benutzen try... except... finally immer bei Stringlisten, beim schreiben/lesen von Dateien (AssignFile, ReWrite usw.), usw, also alles wo ich der Meinung bin es könnte zu Fehlern kommen.
Socke hat geschrieben:
Mo 8. Feb 2021, 08:49
Beides zusammen erzeugt auch in Pascal sehr unleserlichen Code. Entweder kapselst du die einzelnen Fehlerbehandlungen in einzelne Methoden oder nutzt für globale Fehler (EOutOfMemory, EAccessViolation etc.) einen globalen Handler in der Anwendung (z.B. TApplication.OnException).
Das habe ich schon mal gesehen mich aber noch nicht weiter damit beschäftigt.

LG aus dem Schnee, Maik
LG Maik

Windows 10,
- Lazarus 2.2.6 (stable) + fpc 3.2.2 (stable)
- Lazarus 2.2.7 (fixes) + fpc 3.3.1 (main/trunk)

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

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von theo »

Ich habe das auch nie so ganz begriffen.
Was soll bei beim Stringlisten schreiben schief gehen?
OutOfMemory? Dann ist ja eh zu spät, wie soll das Prog darauf reagieren und noch weiterlaufen?

Für Desktop finde ich das meistens übertrieben.
Vielleicht in einem Service/Daemon oder so könnte es Sinn machen bei gleichzeitigem Logging.
I/O, Math etc. ist klar, da ist es sinnvoll.

Oder sehe ich das falsch?

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von Socke »

sstvmaster hat geschrieben:
Mo 8. Feb 2021, 10:14
Und woher weiß ich welche Objekte sich wie verhalten? Gibt es da eine Vorschrift/Anleitung?
Im besten Fall steht das in der Dokumentation. In anderen Sprachen müssen die Exception-Klassen explizit angegeben werden; Pascal ist da sehr frei.
sstvmaster hat geschrieben:
Mo 8. Feb 2021, 10:14
Ich benutzen try... except... finally immer bei Stringlisten, beim schreiben/lesen von Dateien (AssignFile, ReWrite usw.), usw, also alles wo ich der Meinung bin es könnte zu Fehlern kommen.
Da meine Aussage oben sehr optimistisch ist, ist das der beste Weg. Damit hast du einen Großteil der Fehler abgefangen und alles andere behandelst du erst, wenn es einmal aufgetreten ist.
theo hat geschrieben:
Mo 8. Feb 2021, 10:39
Ich habe das auch nie so ganz begriffen.
Was soll bei beim Stringlisten schreiben schief gehen?
OutOfMemory? Dann ist ja eh zu spät, wie soll das Prog darauf reagieren und noch weiterlaufen?
Wenn dein User eine viel zu große Datei öffnet und dein 32-Bit Programm erst einmal 4 GB RAM allokieren muss?
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

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

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von theo »

Socke hat geschrieben:
Mo 8. Feb 2021, 13:28
Wenn dein User eine viel zu große Datei öffnet und dein 32-Bit Programm erst einmal 4 GB RAM allokieren muss?
Sieht du? Das meine ich.
Man muss schon ein ziemliches Horrorszenario aufbauen, damit das irgendwie Sinn macht.
Wenn jemand also aus Versehen auf einem 32bit Rechner (Gibt's das noch?) z.B. eine Linux Installations *.iso öffnet, dann kackt das Desktop-Programm ab, das BS aber nicht.
Na und?

Nicht zu ernst nehmen, aber ein bisschen wahr ist es schon. :wink:

Socke
Lazarusforum e. V.
Beiträge: 3158
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von Socke »

theo hat geschrieben:
Mo 8. Feb 2021, 18:17
Man muss schon ein ziemliches Horrorszenario aufbauen, damit das irgendwie Sinn macht.
Wenn jemand also aus Versehen auf einem 32bit Rechner (Gibt's das noch?) z.B. eine Linux Installations *.iso öffnet, dann kackt das Desktop-Programm ab, das BS aber nicht.
Na und?

Nicht zu ernst nehmen, aber ein bisschen wahr ist es schon. :wink:
Es geht doch am Ende darum, dem Anwender einen Hinweis darauf zu geben, was schief gelaufen ist und was getan werden kann, damit der Fehler nicht mehr auftritt.
Ein "Öffene diese Datei nicht, sonst stürzt das Programm ab" ist nicht wirklich anwenderfreundlich.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Dee
Beiträge: 54
Registriert: Do 10. Jul 2014, 20:56
OS, Lazarus, FPC: Windows 10 Pro 64-bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: Ryzen 5 2600

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von Dee »

Ich würde gerne wieder auf die Thematik zurückkommen.

Was ich schon mal herausgefunden habe, ist, dass das zweite Objekt im Finally-End-Block nicht freigegeben wird, sofern beim Freigeben des ersten Objektes ein Fehler auftreten solle, was aber sehr selten zu sein scheint, aber wäre mir dennoch zu gefährlich. Zumindest hätte ich ein ungutes Gefühl dabei.

Ich habe ein Video dazu gefunden mit Zeitstempel zum gerade genannten Punkt, welches aber auch sich mit anderen Möglichkeiten der Freigaben mehrerer Objekte befasst (Englisch):


Exceptions in Constructors and Destructors - Delphi #30: (https://www.youtube.com/watch?t=822&v=LR4fxHfrXPc)


Lösung wäre hier wieder das Verschachteln mehrerer Try-Blöcke, was den Code wieder schwerer lesbar macht, was ich aber gerne verhindern würde. Hat jemand eine andere Idee, wie man Exception-Handling und das Freigeben mehrerer Objekte unter einem Hut bringen kann, ohne Try-Blöcke zu verschachteln?

EDIT: Außerdem würde man gar nicht herausfinden können, welches Objekt nun den Fehler verursacht bei meinem obigen Code. Schwieriger wird es gerade dann, wenn es zwei Objekte derselben Klasse sind (z.B. TFileStream). Bei welchem FileStream schlug denn das Erstellen einer Datei fehl, wenn ich mit beiden Dateien erstellen wollen würde und diese im selben Try-Except-Block stehen?

-- Dee

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6198
Registriert: So 7. Jan 2007, 10:20
OS, Lazarus, FPC: FPC fixes Lazarus fixes per fpcupdeluxe (win,linux,raspi)
CPU-Target: 32Bit (64Bit)
Wohnort: Burgenland
Kontaktdaten:

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von af0815 »

Wenn ich ein Programm für die Firma schreibe, so kann man davon ausgehen, das 30%-50% vom Code Ausnahmenbehandlung und Errorhandling sind. Das mit dem schwerer lesbar, ja kann man machen. Wenn man daran gewöhnt ist und es halbwegs vernünftig macht bleibt es trotzdem lesbar.

Grundlegend ist es so, das Code der wie bei mir in industriellen Bereichen läuft, weder an Speicherlecks noch an Ausnahmen einfach scheitern darf. Ja er kann Fehler melden, Teile abschalten und ev. sich gezielt Beenden. Aber es muss nachvollziehbar sein, was dazu geführt hat. Das bedingt das man sich mit dem Thema Exceptionhandling sehr genau auseinander setzen muss.

Und eine StringList zu kapseln kann schon Sinn machen. Du könntest ja zB. eine überladene Version benutzen, die eine Funktion in sich birgt, die fehlschlagen kann und dann bist du auf der sicheren Seite. Oder du lädst/schreibst Daten und das System blockiert dir die Datei einfach. Einfache Probleme die aber sauber abgehandelt gehören, sonst hat man gleich ein paar Speicherlecks oder eine tote Applikation.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Dee
Beiträge: 54
Registriert: Do 10. Jul 2014, 20:56
OS, Lazarus, FPC: Windows 10 Pro 64-bit, Lazarus 2.0.10, FPC 3.2.0
CPU-Target: Ryzen 5 2600

Re: Exception-Handling und Freigeben mehrer Objekte

Beitrag von Dee »

Ich bin auf einen interessanten Code gestoßen, der es ermöglicht, Objekte freizugeben,
selbst wenn ein Fehler im Finally-End-Block auftritt.

Quelle: https://code-examples.net/de/q/61339

Code: Alles auswählen

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;
Ich habe es dann auf mein Beispiel angewandt und etwas erweitert (Kritik sehr erwünscht).

Code: Alles auswählen

// ...

type
  EObjectCreation = class(Exception);
  ENilAndFree = class(Exception);
  EFreeingFailedNotification = class(Exception); 

// ... 

function SafeFreeAndNil(AnObject: TObject; Fail: Boolean = False): Boolean;
begin
  try
    if Fail then
    begin
      raise ENilAndFree.Create('Freeing object failed');
    end;
    FreeAndNil(AnObject);
    Result := True;
  except
    Result := False;
  end;
end; 

// ...

procedure DoSomethingWithStringLists;
const
  FAIL = True;
  SUCCEED = False;

var
  SL1, SL2: TStringList;
  GotFreed1, GotFreed2: Boolean;
begin
  SL1 := nil;
  SL2 := nil;

  try
    try
      try
        SL1 := TStringList.Create;
        // simulating error on creation
        raise EObjectCreation.Create('Creation of Object failed');
        SL2 := TStringList.Create;
      except
        on E: EObjectCreation do
        begin
          ShowMessage(E.ClassName + LineEnding + E.Message + LineEnding + {$I %FILE%} + '->' +{$I %CURRENTROUTINE%});
        end;
      end;
    finally
      // Source: https://code-examples.net/de/q/61339

      // simulating error on freeing with the FAIL flag
      GotFreed1 := SafeFreeAndNil(SL1, FAIL);
      GotFreed2 := SafeFreeAndNil(SL2, SUCCEED);

      if not (GotFreed1 and GotFreed2) then
      begin
        raise EFreeingFailedNotification.Create('One of the objects could not get freed');
      end;
    end;
  except
    on E: EFreeingFailedNotification do
    begin
      ShowMessage('Some objects could not get freed.' + LineEnding + {$I %FILE%} + '->' +{$I %CURRENTROUTINE%});
    end;
    on E: Exception do
    begin
      ShowMessage('Uknown Error: ' + E.ClassName + LineEnding + E.Message + LineEnding + {$I %FILE%} + '->' +{$I %CURRENTROUTINE%});
    end;
  end;
end;
Damit sollte es möglich sein, Speicherlecks zu minimieren und auf Verschachtelungen zu verzichten. Für bessere Lesbarkeit könnte der äußerste Try-Except-Block auch weggelassen, denke ich.

-- Dee

Antworten