Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Objekte freigeben (https://www.delphipraxis.net/129783-objekte-freigeben.html)

SteffenSchm 25. Feb 2009 13:47


Objekte freigeben
 
Hallo zusammen,

ich bekomme bei meinem Projekt eine Fehlermeldung (EInvalidPointer) bei Beendigung des Programmes.

Die Ursache liegt darin, dass Objekte mit Free freigegeben werden, die bereits an anderer Stelle freigegeben wurden.

Nun meine Frage:

Wie kann ich feststellen, ob ein Objekt schon freigegeben wurde? Leider wird ein Objekt bei der Freigabe offensichtlich nicht automatisch Nil gesetzt.

Der Aufruf von TObject.Free ist unkritisch, wenn ein Objekt noch nicht initialisirt wurde. Wenn es aber bereits freigegeben wurde, führt der nochmalige Aufruf von Free zum Absturz. Wie kann man das verhindern?

Für Hinweise und Ratschläge wäre ich dankbar!

nuclearping 25. Feb 2009 13:49

Re: Objekte freigeben
 
Delphi-Quellcode:
FreeAndNil (Object);

if Assigned (Object) then
  ...

SteffenSchm 25. Feb 2009 14:27

Re: Objekte freigeben
 
Danke für den Hinweis. Da hätte ich auch selber drauf kommen können. Habs auch gleich ausprobiert. Es hilft aber nicht in dem folgenden Fall.

Ich habe eine von TWorldBox und damit von TComponent abgeleitete Klasse.
Delphi-Quellcode:
TMyBox = class(TWorldBox)
  public
    { Public-Deklarationen }
    Parameter      : TParmeter;
    destructor Destroy; override;
   end;

destructor TMyBox.Destroy;
begin
  FreeAndNil(Parameter);
  inherited Destroy;
end;
Ein oder mehrere Komponenten vom Typ TMyBox erzeuge ich im Programm dynamisch aus einem übergeordneten Objekt TArea heraus. Dieses Objekt weist MyBox auch verschienen Parametersätze zu (ParameterA, ParameterB, ParameterC). Da ich nicht weiss, welcher Parametersatz am Ende in MyBox verlinkt ist, gebe ich im Destructor von TArea alle drei frei.

Delphi-Quellcode:
  TArea = class(TObject)
    MyBox : TMyBox;
    ParameterA,
    ParameterB,
    ParamterC: TParameter;
    constructor Create;
    destructor Destroy; override;
  end;

constructor TArea.Create;
begin
  MyBox         := TMyBox.Create(MainForm);
  ParameterA    := MyBox.Parameter;
  ParameterB    := TParameter.Create;
  ParameterC    := TParameter.Create;
end;

destructor TCalcArea.Destroy;
begin
  FreeAndNil(ParmeterA);
  FreeAndNil(ParmeterB);
  FreeAndNil(ParmeterC);
  FreeAndNil(MyBox);
 inherited Destroy;
end;
Beim Programmende gibt es aber Probleme. Die Komponente MyBox wird von Delphi schon freigegeben bevor der Destructor von TCalcArea aufgerufen wird. Und Delphi scheint nicht über FreeAndNil freizugeben (Warum eigentlich nicht). Zusätzlich gibt es Probleme beim Freigeben des Parametersatzes der schon über MyBox freigegeben wurde.

Was kann man daran ändern?

himitsu 25. Feb 2009 14:39

Re: Objekte freigeben
 
Dort wo du etwas freigibt, die Variable auf NIL setzen

Delphi-Quellcode:
ParameterA := MyBox.Parameter;

FreeAndNil(ParmeterA);

If Assign(ParmeterA) Then FreeAndNil(ParmeterA);
das letzte FreeAndNil wird nicht ausgeführt, da Parameter1 schon vorher freigegeben und auch als frei markiert (NIL) wurde.

SteffenSchm 25. Feb 2009 14:53

Re: Objekte freigeben
 
Erst mal danke, dass ihr Euch da hineindenkt!

Das mit dem FreeAndNil habe ich schon verstanden. (Brauch man danach die Abfrage mit Assigned eigentlich noch oder testet Free bzw. FreeAndNil nicht selbt auch Nil?)

Das Problem in meinem Fall ist aber wahrscheinlich, dass beim Beenden des Programms zuerst die Komponenete MyBox von Delphi freigegeben wird und danach eben nicht auf Nil gesetzt wird. Genauso ist der innerhalb von MyBox verlinkte Paramter nicht Nil.

himitsu 25. Feb 2009 15:06

Re: Objekte freigeben
 
FreeAndNil testet eigentlich nichts (glaub ich)

FreeAndNil macht auch nichts anderes, als

Delphi-Quellcode:
procedure FreeAndNil(Obj)
begin
  Temp := Obj;
  Obj := nil;
  Temp.Free;
end;

jfheins 25. Feb 2009 15:08

Re: Objekte freigeben
 
Schon - aber Free testet ungefähr so:

Delphi-Quellcode:
procedure Free
begin
if Self <> nil then
  Destroy;
end;
;)

Luckie 25. Feb 2009 16:46

Re: Objekte freigeben
 
Zitat:

Zitat von SteffenSchm
Delphi-Quellcode:
MyBox         := TMyBox.Create(MainForm);

FreeAndNil(MyBox);

Du hast also Owner deine Form angegeben. Somit übernimmt auch deine Form das Zerstören des Objektes. Du versuchst es aber selber noch mal frei zugeben. Entweder gibst du beim Erzeugen keinen Owner an und kümmerst dich selber ums Freigeben oder aber du gibst einen Owner an und überlässt ihm das Freigeben.

SteffenSchm 25. Feb 2009 16:49

Re: Objekte freigeben
 
Hi Luckie,

das klingt einleuchtend und könnte mein Problem beheben. Danke für den Hinweis.

mjustin 25. Feb 2009 17:43

Re: Objekte freigeben
 
Zitat:

Zitat von jfheins
Schon - aber Free testet ungefähr so:

Delphi-Quellcode:
procedure Free
begin
if Self <> nil then
  Destroy;
end;
;)

Hilft auch nicht immer:

Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  X: TObject;

  // aus SysUtils.pas
  procedure FreeAndNil(var Obj);
  var
    Temp: TObject;
  begin
    Temp := TObject(Obj);
    Pointer(Obj) := nil;
    Temp.Free;
  end;

begin
  FreeAndNil(X)
end;
wirft eine 'Externe Exception C00001D' (in D6, und etwas ähnliches in D2009).

Warnungen oder Hinweise wegen der nicht initialisierten Variable X gibt es keine - da muss man halt aufpassen :)

sirius 25. Feb 2009 18:36

Re: Objekte freigeben
 
Zitat:

Zitat von mjustin

wirft eine 'Externe Exception C00001D' (in D6, und etwas ähnliches in D2009).

Warnungen oder Hinweise wegen der nicht initialisierten Variable X gibt es keine - da muss man halt aufpassen :)

Bei lokalen Variablen ist das auch klar. Das tritt aber nur bei lokalen Variablen auf. Und da passt man sowieso auf. bzw. hat die entsprechende Methode im Überblick.

himitsu 25. Feb 2009 18:46

Re: Objekte freigeben
 
Variablen sollte man eh immer Initialisieren. (bevor man lesend drauf zugreifen könnte)

sx2008 25. Feb 2009 19:49

Re: Objekte freigeben
 
Diejenige Klasse, die eine oder mehrere Objekte erzeugt, sollte normalerweise auch die Verantwortung für die Freigabe übernehmen.
Eine Klasse, die in ihrem Konstruktor Unterobjektte erzeugt sollte dann die Verantwortung für ihre Freigabe im Destruktor übernehmen.

Die Anwendung von FreeAndNil() ist häufig ein Kennzeichen dafür, dass die Verantwortlichkeiten nicht sauber geregelt sind.
Delphi-Quellcode:
TMyBox = class(TWorldBox)
  public
    { Public-Deklarationen }
    Parameter      : TParmeter;
    destructor Destroy; override;
   end;

destructor TMyBox.Destroy;
begin
  FreeAndNil(Parameter); // Falsch! 
  // die Variable Parameter wurde ganz offensichtlich von einer anderen Klasse erstellt
  // TMyBox trägt nicht die Verantwortung für die Freigabe
  // FreeAndNil() wäre sowieso sinnlos, da die Variable Parameter demnächst "out of scope" geht

  inherited Destroy;
end;
Also nicht blind mit FreeAndNil() um sich schiessen, sondern genau überlegen.

SteffenSchm 26. Feb 2009 09:35

Re: Objekte freigeben
 
Vielen Dank für die vielen Hinweise und die interessante Diskussion. Noch ein paar Erläuterungen:

Die Klasse TMyBox stellt Daten in Abhängigkeit von Parametern da. Das Objekt Parameter wird in dieser Klasse im constructor erzeugt und auch im destructor wieder freigegeben. (@sx2008:Das FreeAndNil an dieser Stelle ist tatsächlich nutzlos. Ich habe es deshalb wieder zurückgenommen.) Die Klasse sieht jetzt so aus:

Delphi-Quellcode:
TMyBox = class(TWorldBox)
  public
    { Public-Deklarationen } 
    Parameter      : TParmeter;
    construcor Create(AOwner: TComponent); override;
    destructor Destroy; override;
   end;

constructor TMyBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parameter     :=TParameter.Create;
end;

destructor TMyBox.Destroy;
begin
  Parameter.Free;
  inherited Destroy;
end;
Diese Klasse nutze ich für verschiedene Projekte. Ich will sie deshalb für das aktuelle Problem nichr grundlegend ändern. Im aktuellen Projekt soll es aber möglich sein, zwischen drei unterschiedlichen Darstellungen umzuschalten. Die Art der drei Darstellungen ist in drei Objekten TParameter gespeichert. Den aktuell zu verwendenden weise ich dann MyBox.Parameter zu. Das grundlegende Problem war, dass ich nach Freigabe von MyBox nicht wusste, welcher der drei Parameterobjekte bereits freigegeben ist.

Ich habe mich jetzt dazu entschlossen das aufrufende Objekt von TMyBox abzuleiten. Das ganze sieht jetzt so aus:

Delphi-Quellcode:
  TArea = class(TMyBox)
  public
    ParameterA,
    ParameterB,
    ParameterC: TParameter;
    construcor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  end;

constructor TArea.Create(AOwner: TComponent);
begin
  inherited Create(MainForm);
  Parent  := MainForm.TabControl;
 
  ParameterA := Parameter;
  ParameterB := TParameter.Create;
  Parameterc := TParameter.Create;
 end;

destructor TCalcArea.Destroy;
begin
  FreeAndNil(ParameterA);
  FreeAndNil(ParameterB);
  FreeAndNil(ParameterC);
  Parameter:=Nil;
  inherited Destroy;
end;
Ich habe damit immer drei erzeugte TParameter-Objekte aber vier Zeiger die darauf zeigen. Im destructor von TArea gebe ich aller drei erzeugten Parameter frei. Den vierten Zeiger muss ich aber zusätzlich auf Nil setzen, ansonsten würde Parameter.Free im destructor von TMyBox zum Fehler führen.

Das ganze scheint jetzt so zu funktionieren. Für Hinweise und Anregungen bin ich dennoch dankbar !

shmia 26. Feb 2009 17:49

Re: Objekte freigeben
 
Diese Deklaration ist nicht sicher:
Delphi-Quellcode:
TMyBox = class(TWorldBox)
 public
   { Public-Deklarationen } 
   Parameter      : TParameter;
   construcor Create(AOwner: TComponent); override;
   destructor Destroy; override;
end;
Das Problem ist, dass jeder von Aussen die Variable Parameter umbiegen kann.
Das führt zwangsläufig zu Fehlern.

Über ein Property kann man die Sache aber wasserdicht machen:
Delphi-Quellcode:
TMyBox = class(TWorldBox)
 private
   FParameter : TParameter;
   procedure SetParameter(value:TParameter);

 public
   { Public-Deklarationen } 
   construcor Create(AOwner: TComponent); override;
   destructor Destroy; override;
   property Parameter : TParameter read FParameter write SetParameter;
end;

procedure TMyBox.SetParameter(value:TParameter);
begin
  FParameter.Assign(value);
end;
Die Klasse TParameter muss von TPersistent abgeleitet werden und die Methode Assign muss überschrieben werden.
Mehr zu Assign() - leider auf englisch http://delphi.about.com/od/course/a/delphi_oop21.htm

sirius 26. Feb 2009 19:57

Re: Objekte freigeben
 
Anstatt Assign besser AssignTo überschreiben. aber vielleicht braucht er ja keinen Setter. Der gezeigte Code oben ist "schlecht", aber für Verbesserungen braucht man hier mehr Informationen.

SteffenSchm 27. Feb 2009 07:27

Re: Objekte freigeben
 
Danke für die Hinweise.

Wenn ich das richtig verstehe, wird mit Assign (oder AssignTo) dann der Inhalt des Objektes auf ein neues Objekt kopiert. Ich brauche dann tatsächlich vier erzeugte und auch wieder freizugebende Objekte vom Typ TParameter. Dann ist das mit dem Freigeben natürlich klar geregelt und erzeugt keine Konflikte.

Ich hatte versucht, nur einen Zeiger auf das aktuelle Objekt Parameter zu setzen (bzw. umzubiegen). Ich dachte das wäre effizienter. Aber es ist wohl nicht ganz sauber programmiert.

Noch eine Frage: Wenn ich den Parameter in MyBox über ein property anspreche, warum kann ich dann nicht in der Methode SetParameter den Code reinschreiben, den ich in die Methode Assign bzw. AssignTo der von TPersistent abgeleiteten Klasse TParameter schreiben soll. Wäre zumindest für mich dann einfacher zu lesen und zu verstehen.

SteffenSchm 27. Feb 2009 16:17

Re: Objekte freigeben
 
Das ganze wirft bei mir jetzt eine prinzipielle Frage auf.

Ich "missbrauche" Ableitungen von TObject auch an anderen Stellen noch als Zeiger. Am besten vielleicht an einem Beispiel zu erklären:

Ich habe eine Datenstruktur ähnlich einer Master/Detail-Tabelle in einer Datenbank. Die Mastertabelle (bzw. -liste) ist eine Objektliste mit z.B. Werkstücken. Sie enthält Verweise auf eine Detailtabelle (auch hier wieder eine Objektliste) in der der Werkstoff des Werkstücks steht.

Für die Werkstoffe habe ich ein Objekt vom Typ TMat = class(TObject) definiert. Diese Objekte werden erzeugt, in der Liste gespeichert und am Ende auch wieder freigegeben.

Innerhalb des Objektes Werkstück benutze ich auch TMat, hier aber nur als Zeiger auf ein Element der Materialliste. Es wird vom Objekt Werkstück weder erzeugt noch freigegeben.

Ist das aus OOP-Gesichtspunkten so o.k., oder sollte man das prinzipiell anders machen.

Vielleicht schaut jemand mit Ahnung noch mal in dieses Thema rein und hat eine Anmerkung bzw. einen Hinweis. Besten Dank!

sx2008 28. Feb 2009 10:35

Re: Objekte freigeben
 
Zitat:

Zitat von SteffenSchm
Noch eine Frage: Wenn ich den Parameter in MyBox über ein property anspreche, warum kann ich dann nicht in der Methode SetParameter den Code reinschreiben, den ich in die Methode Assign bzw. AssignTo der von TPersistent abgeleiteten Klasse TParameter schreiben soll. Wäre zumindest für mich dann einfacher zu lesen und zu verstehen.

Es ist besser, eine Klasse die Möglichkeit zu geben, Objekte zu kopieren, als dass man diese Aufgabe in sehr begrenzter Form einer anderen Klasse überlässt.
Die Klasse TParameter weiss selbst am Besten, wie sich sich kopieren soll.

Assign() oder AssignTo():
Wenn eine Klasse Objekte von sich selbst kopieren soll, dann verwendet man immer Assign().
AssignTo() kommt nur in ganz bestimmten Sonderfällen zum Einsatz.
Angenommen, man hat eine neue Klasse namens TSuperBitmap geschrieben.
Dann möchte man sicher die SuperBitmap auch auf ein normales TBitmap-Objekt kopieren:
Delphi-Quellcode:
bitmap.Assign(superbitmap);
Jetzt ergibt sich nur das Problem, dass die Klasse TBitmap unsere neue Klasse TSuperbitmap nicht kennt.
Und man kommt auch an TBitmap.Assign() nicht heran, weil der Code in der VCL steckt.
Aber man kann die Sache umdrehen und TSuperBitmap.AssignTo() überschreiben und so das Dilemma lösen.

schöni 28. Feb 2009 11:32

Re: Objekte freigeben
 
Hallo,

Keine Ahnung, ob es im konkreten Fall hilft, aber ich gebe Objekte grundsätzlich nach Stack-Prinzip frei. In umgekehrter Reihenfolge der Initialisierung:

Delphi-Quellcode:
var
 Object1: TMyFirstClass;
 Object2: TMySecondClass;
 Object3: TMyThirdClass;

begin
  Object1 := TMyFirstClass.Create;
  Object2 := TMySecondClass.Create;
  Object3 := TMyThierClass.Create;
  ...
  if Assigned(Object3) then FreeAndNil(Object3);
  if Assigned(Object2) then FreeAndNil(Object2);
  if Assigned(Object1) then FreeAndNil(Object1);
end.

Muetze1 28. Feb 2009 12:27

Re: Objekte freigeben
 
... und die IF Abfrage ist dabei nur Zeitverschendung...

mjustin 28. Feb 2009 15:28

Re: Objekte freigeben
 
Bessere Absicherung gegen Memory Leaks erhält man mit try .. finally Blöcken:
Delphi-Quellcode:
  Object1 := TMyFirstClass.Create;
  try
    // use Object1 
    ...
    Object2 := TMySecondClass.Create;
    try
      // use Object2 
      ...
      Object3 := TMyThirdClass.Create;
      try
        // use Object3 
        ...
      finally
        FreeAndNil(Object3);
      end;
    finally
      FreeAndNil(Object2);
    end;
  finally
    FreeAndNil(Object1);
  end

xZise 28. Feb 2009 15:44

Re: Objekte freigeben
 
Zitat:

Zitat von mjustin
Bessere Absicherung gegen Memory Leaks erhält man mit try .. finally Blöcken:[...]

Und jetzt zeig mir mal bitte, wie du du das im Finally Block packst.

Zu dem Parameterproblem könntest du zwei Wege verwenden. Entweder FParameter von TMyBox ist eine "lokale" Kopie, oder TMyBox interessiert sich nicht um den Parameter sondern hat nur ein Zeiger drauf. Das heißt nichts zu Anfang erstellen und am Ende freigeben. Aber dann natürlich beim jedem Zugriff zuerst testen, ob der Zeiger valid ist. Ich würde dabei ersteres bevorzugen, wenn das nicht ZU viele Daten sind.

MfG
xZise

SteffenSchm 28. Feb 2009 16:00

Re: Objekte freigeben
 
Ich danke Euch allen (besonders shmia, sx2008 und xZise) für die nützlichen Hinweise.

Ich habe dabei einiges gelernt.

mjustin 28. Feb 2009 17:45

Re: Objekte freigeben
 
Zitat:

Zitat von xZise
Und jetzt zeig mir mal bitte, wie du du das im Finally Block packst.

Das war eigentlich schon alles, was es zu zeigen gab. Mehr Magie ist dazu nicht erforderlich, und auch nicht mehr als drei finally ... oder was meintest Du mit 'du du das', was da noch gepackt werden soll ?

Blup 3. Mär 2009 12:36

Re: Objekte freigeben
 
Damit "das" übersichtlich bleibt und nicht zu viele Blöcke entstehen, besser so:
Code:
Object1 := nil;
Object2 := nil;
Object3 := nil;
try
  Object1 := TMyFirstClass.Create;
  // use Object1 
  ...
  Object2 := TMySecondClass.Create;
  // use Object2 
  ...
  Object3 := TMyThirdClass.Create;
  // use Object3 
  ...
finally
  FreeAndNil(Object3);
  FreeAndNil(Object2);
  FreeAndNil(Object1);
end;
Bei der Freigabe von Objekten sollte eigentlich kein Fehler auftreten.


Alle Zeitangaben in WEZ +1. Es ist jetzt 19:13 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz