![]() |
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! |
Re: Objekte freigeben
Delphi-Quellcode:
FreeAndNil (Object);
if Assigned (Object) then ... |
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:
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.
TMyBox = class(TWorldBox)
public { Public-Deklarationen } Parameter : TParmeter; destructor Destroy; override; end; destructor TMyBox.Destroy; begin FreeAndNil(Parameter); inherited Destroy; end;
Delphi-Quellcode:
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.
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; Was kann man daran ändern? |
Re: Objekte freigeben
Dort wo du etwas freigibt, die Variable auf NIL setzen
Delphi-Quellcode:
das letzte FreeAndNil wird nicht ausgeführt, da Parameter1 schon vorher freigegeben und auch als frei markiert (NIL) wurde.
ParameterA := MyBox.Parameter;
FreeAndNil(ParmeterA); If Assign(ParmeterA) Then FreeAndNil(ParmeterA); |
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. |
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; |
Re: Objekte freigeben
Schon - aber Free testet ungefähr so:
Delphi-Quellcode:
;)
procedure Free
begin if Self <> nil then Destroy; end; |
Re: Objekte freigeben
Zitat:
|
Re: Objekte freigeben
Hi Luckie,
das klingt einleuchtend und könnte mein Problem beheben. Danke für den Hinweis. |
Re: Objekte freigeben
Zitat:
Delphi-Quellcode:
wirft eine 'Externe Exception C00001D' (in D6, und etwas ähnliches in D2009).
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; Warnungen oder Hinweise wegen der nicht initialisierten Variable X gibt es keine - da muss man halt aufpassen :) |
Re: Objekte freigeben
Zitat:
|
Re: Objekte freigeben
Variablen sollte man eh immer Initialisieren. (bevor man lesend drauf zugreifen könnte)
|
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:
Also nicht blind mit FreeAndNil() um sich schiessen, sondern genau überlegen.
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; |
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:
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.
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; Ich habe mich jetzt dazu entschlossen das aufrufende Objekt von TMyBox abzuleiten. Das ganze sieht jetzt so aus:
Delphi-Quellcode:
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.
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; Das ganze scheint jetzt so zu funktionieren. Für Hinweise und Anregungen bin ich dennoch dankbar ! |
Re: Objekte freigeben
Diese Deklaration ist nicht sicher:
Delphi-Quellcode:
Das Problem ist, dass jeder von Aussen die Variable Parameter umbiegen kann.
TMyBox = class(TWorldBox)
public { Public-Deklarationen } Parameter : TParameter; construcor Create(AOwner: TComponent); override; destructor Destroy; override; end; Das führt zwangsläufig zu Fehlern. Über ein Property kann man die Sache aber wasserdicht machen:
Delphi-Quellcode:
Die Klasse TParameter muss von TPersistent abgeleitet werden und die Methode Assign muss überschrieben werden.
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; Mehr zu Assign() - leider auf englisch ![]() |
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.
|
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. |
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! |
Re: Objekte freigeben
Zitat:
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:
Jetzt ergibt sich nur das Problem, dass die Klasse TBitmap unsere neue Klasse TSuperbitmap nicht kennt.
bitmap.Assign(superbitmap);
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. |
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. |
Re: Objekte freigeben
... und die IF Abfrage ist dabei nur Zeitverschendung...
|
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 |
Re: Objekte freigeben
Zitat:
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 |
Re: Objekte freigeben
Ich danke Euch allen (besonders shmia, sx2008 und xZise) für die nützlichen Hinweise.
Ich habe dabei einiges gelernt. |
Re: Objekte freigeben
Zitat:
|
Re: Objekte freigeben
Damit "das" übersichtlich bleibt und nicht zu viele Blöcke entstehen, besser so:
Code:
Bei der Freigabe von Objekten sollte eigentlich kein Fehler auftreten.
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; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:15 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz