Objekte korrekt freigeben

Für Fragen von Einsteigern und Programmieranfängern...
Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Objekte korrekt freigeben

Beitrag von Marsmännchen »

Hi,

ich beschäftige mich gerade mit Polymorphismus unter Freepascal und habe mir dazu ein simples Beispielprogramm gebastelt. Das habe ich dann beim Freigeben der Objekte gerasht (SigSegV), da ich vorher die Objektreferenzen fröhlich getauscht habe. Das ist mir soweit klar. Was mir nicht klar ist: wie man diese Freigaben sauber programmiert.
Der Code sieht so aus (hier nur das entscheidende Fragment, der Rest sind Testausgaben von überschriebenen Funktionen):

Code: Alles auswählen

 
EinTier:= TAnimal.Create;
Fido := TDog.Create;
Miezi := TCat.Create;
{TDog und TCat sind von TAnimal abgeleitet}
 
EinTier := Fido;
EinTier := Miezi;
 
FreeAndNil(EinTier);
FreeAndNil(Fido);
FreeAndNil(Miezi); {Hier knallt es}   
 


Ich bin mir sicher, dass es zum Absturz kommt, weil ich "Miezi" freigeben will, obwohl das schon durch "EinTier" erfolgt ist. Okay, da kann man auch sagen: pass halt besser auf (was schwierig sein kann, wenn es um mehr geht als so ein simples Demo-Ding). Man könnte natürlich auch solche Freigaben immer in ein Try packen. Oder gibt es eine Funktion, die erst schaut, ob der Speicher, auf den die Referenz verweist, schon freigegeben ist? Oder gibt es für sowas ein BestPractice oder ein Turnaround oder was auch immer? Wie macht ihr sowas?
Ich mag Pascal...

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6209
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: Objekte korrekt freigeben

Beitrag von af0815 »

Sowas ?

Code: Alles auswählen

if assigned(Miezi) then FreeAndNil(Miezi);
Zuletzt geändert von af0815 am Mo 19. Sep 2016, 15:51, insgesamt 1-mal geändert.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: Objekte korrekt freigeben

Beitrag von Michl »

Offtopic:
Wenn du eine Instanz von EinTier erstellst, erzeugst du ein Speicherleck, sobald du eine andere (abgeleitete) Instanz dieser zuweist.
Versuche möglichst entgegengesetzt der Erstellung der Instanzen, diese wieder frei zu geben.

Würde dann so aussehen:

Code: Alles auswählen

// EinTier:= TAnimal.Create;  // diese Zeile entfernt
Fido := TDog.Create;
Miezi := TCat.Create;
 
EinTier := Fido;
EinTier := Miezi;
 
FreeAndNil(Miezi);
FreeAndNil(Fido);

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: Objekte korrekt freigeben

Beitrag von wp_xyz »

af0815 hat geschrieben:Sowas ?

Code: Alles auswählen

if assigned(Miezi) then FreeAndNil(Miezi);

Bedeutet das "Doppelt" im folgenden Post, dass das "doppelt gemoppelt" ist? Denn FreeAndNil prüft bereits auf Nil. Genauso wie .Free allein übrigens.

Ich glaube, mit nil kann man das geschilderte Problem nicht beseitigen. Der Grundstein des Crashs wird in der Zeile "FreeAndNil(EinTier)" gelegt, als Folge ist das Tier mit Namen "EinTier" "getötet" und dessen Name "EinTier" "gelöscht" (= nil). Dabei handelt es sich um die Katze, deren anderer Name "Miezi" aber unverändert geblieben ist. Es gibt keine Möglichkeit zu prüfen, ob "Miezi" sich auf ein lebendes oder totes Tier bezieht.

Mit Referenzzählung könnte man das Problem umgehen: Jedes Tier bekommt ein Halsband mit integriertem Zähler für die Anzahl seiner Leben. Immer wenn das tier einen neuen Namen erhält, wird der Zähler um 1 erhöht. Wenn jemand das Tier tötet, wird zunächst nur der Zählerstand um 1 verringert. Erst wenn der Zähler auf null steht, schlägt seine Stunde...

martin_frb
Beiträge: 572
Registriert: Mi 25. Mär 2009, 21:12
OS, Lazarus, FPC: Laz trunk / fpc latest release / Win and other
CPU-Target: mostly 32 bit

Re: Objekte korrekt freigeben

Beitrag von martin_frb »

af0815 hat geschrieben:Sowas ?

Code: Alles auswählen

if assigned(Miezi) then FreeAndNil(Miezi);


"Doppelt" erklärt: FreeAndNil beinhaltet den Test "if assigned"

Aber "If assigned" tested nur auf "nil"

Code: Alles auswählen

#
miezi := nil;
FreeAndNil(Miezi); // kein Problem
 
miezi := nil;
if assigned(Miezi) then Miezi.Destroy; // kein Problem
 
miezi := nil;
Miezi.Destroy; // BOOM
 


--------------------

Code: Alles auswählen

#
miezi := Tanimal.create();
miezi := tier;
FreeAndNil(Miezi);
FreeAndNil(tier); // boom
 
// oder, genau das gleiche
miezi := Tanimal.create();
Miezi.Destroy;
Miezi.Destroy; // BOOM
 


hier muss man einfach aufpassen. Da gibt es kein Schutz- / Test- funktion

Objekte sind internal Referenzen.

D.h. Die Variable miezi enthält nur eine Adresse (Pointer), und an der Adresse befinden sich dann die Daten des Objekt.
miezi := tier;
Nun enthalten beide dieselben Adresse, zeigen auf das selbe Objekt.
FreeAndNil(tier);
Dann ist das objekt weg, aber miezi enthält immer noch die Adresse.

Da wo das Objekt mal war stehen jetzt zufällige Daten (manchmal sogar noch das original).
Beim Zugriff auf miezi kann dann alles mögliche passieren.

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Objekte korrekt freigeben

Beitrag von Marsmännchen »

Michl hat geschrieben:Offtopic:
Wenn du eine Instanz von EinTier erstellst, erzeugst du ein Speicherleck, sobald du eine andere (abgeleitete) Instanz dieser zuweist.
Versuche möglichst entgegengesetzt der Erstellung der Instanzen, diese wieder frei zu geben.



Du hast Recht, das werde ich mir merken. Wenn ich eine Variable der Elternklasse erstelle, um Objekte der Kindklassen zu halten, dann muss die ja auch vorher nicht unbedingt mit einem Objekt der Elternklasse belegt werden. Am besten auf nil lassen und vor der Verwendung darauf prüfen, ob sie zu diesem Zeitpunkt auf ein konkretes Objekt verweist, richtig?
Ich mag Pascal...

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Objekte korrekt freigeben

Beitrag von Marsmännchen »

wp_xyz hat geschrieben:Mit Referenzzählung könnte man das Problem umgehen: Jedes Tier bekommt ein Halsband mit integriertem Zähler für die Anzahl seiner Leben. Immer wenn das tier einen neuen Namen erhält, wird der Zähler um 1 erhöht. Wenn jemand das Tier tötet, wird zunächst nur der Zählerstand um 1 verringert. Erst wenn der Zähler auf null steht, schlägt seine Stunde...


In meinem Buch steht, dass es für Delphi Compiler geben soll, die ARC beherrschen. Ich schätze, für Freepascal ist das nicht geplant. Falls man vorher weiß, dass man viel mit Referenztausch arbeiten will, könnte man ja mal überlegen, ob sich so ein Referenzzähler nicht implementieren lässt. Aber das ist (noch) nicht mein Level.

Okay, es scheint also mehr darauf anzukommen, dass man halt aufpasst. Wer es einfacher will, muss sich wohl eine Sprache mit Garbage Collection suchen. Was mich momentan noch etwas verwirrt sind diese unterschiedlichen Freigabefunktionen. Ich hatte erst Free benutzt und als das Programm krachte, es läge daran, das ich vergessen hätte die Referenzen auf nil zu setzen. Deswegen habe ich auf FreeAndNil umgestellt. Als das nichts brachte wurde mir klar, dass es daran liegt, dass ich versuche auf einen unbestimmten Speicherbereich zuzugreifen. Jetzt lese ich hier, dass Free vorher auch auf nil prüft. Wozu also FreeAndNil? Dann gibt es auch noch Destroy (ja, ich weiß, das ist der Destruktor). Das Zusammenspiel der Funktionen beim Objektaufräumen bzw. ihre Unterschiede muss ich mir nochmal in Ruhe ansehen...
Ich mag Pascal...

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: Objekte korrekt freigeben

Beitrag von Michl »

Marsmännchen hat geschrieben:Wenn ich eine Variable der Elternklasse erstelle, um Objekte der Kindklassen zu halten, dann muss die ja auch vorher nicht unbedingt mit einem Objekt der Elternklasse belegt werden. Am besten auf nil lassen und vor der Verwendung darauf prüfen, ob sie zu diesem Zeitpunkt auf ein konkretes Objekt verweist, richtig?
Soweit richtig. Wie gesagt, sobald du eine Referenz übergibst (das geschieht bei EinTier := Fido), ist der Zeiger auf die vorherige Instanz (EinTier) weg. Wenn keine andere Variable auf diese Instanz zeigt, müsste sie zuvor frei gegeben werden oder man hat ein Speicherleck.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: Objekte korrekt freigeben

Beitrag von Michl »

Marsmännchen hat geschrieben:Jetzt lese ich hier, dass Free vorher auch auf nil prüft. Wozu also FreeAndNil?

Vielleicht hilft dieses Beispiel zur Verdeutlichung:

Code: Alles auswählen

program Project1;
 
uses sysutils;
 
type
  TMyClass = class
  end;
 
var
  MyClass: TMyClass;
 
begin
  MyClass := Nil;
  if not Assigned(MyClass) then
    MyClass := TMyClass.Create;
  FreeAndNil(MyClass);
 
  if not Assigned(MyClass) then
    MyClass := TMyClass.Create;
  MyClass.Free;
 
  if not Assigned(MyClass) then
    MyClass := TMyClass.Create;
  MyClass.Free;
end.
Im letzten MyClass.Free gibt es ein SIGSEGV. Da beim ersten MyClass.Free der Zeiger nicht wieder auf Nil gesetzt wird (er zeigt weiterhin auf eine nun ungültige Stelle im Speicher), wird bei Assigned(MyClass) True zurückgeliefert und keine neue Instanz von TMyClass erstellt. MyClass.Free versucht nun eine ungültige Instanz freizugeben.

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Objekte korrekt freigeben

Beitrag von Marsmännchen »

Michl hat geschrieben:Wie gesagt, sobald du eine Referenz übergibst (das geschieht bei EinTier := Fido), ist der Zeiger auf die vorherige Instanz (EinTier) weg. Wenn keine andere Variable auf diese Instanz zeigt, müsste sie zuvor frei gegeben werden oder man hat ein Speicherleck.

Also sich am besten angwöhnen: Sobald ich ein Create in den Code geschrieben habe, ans Ende der Funktion gehen und gleich das Free hinzuschreiben, bevor ich dazwischen die eigentliche Funktionalität mache. Dann kann man das nicht so leicht vergessen 8).
Ich mag Pascal...

Michl
Beiträge: 2505
Registriert: Di 19. Jun 2012, 12:54

Re: Objekte korrekt freigeben

Beitrag von Michl »

Bild

Code: Alles auswählen

type
  TLiveSelection = (lsMoney, lsChilds, lsTime);
  TLive = Array[0..1] of TLiveSelection; 

Marsmännchen
Beiträge: 294
Registriert: So 4. Mai 2014, 21:32
OS, Lazarus, FPC: Windows 10 64bit, Lazarus 2.0.10
CPU-Target: 64bit
Wohnort: Oranienburg

Re: Objekte korrekt freigeben

Beitrag von Marsmännchen »

Michl hat geschrieben:Im letzten MyClass.Free gibt es ein SIGSEGV. Da beim ersten MyClass.Free der Zeiger nicht wieder auf Nil gesetzt wird (er zeigt weiterhin auf eine nun ungültige Stelle im Speicher), wird bei Assigned(MyClass) True zurückgeliefert und keine neue Instanz von TMyClass erstellt. MyClass.Free versucht nun eine ungültige Instanz freizugeben.

Alles klar, also ist FreeAndNil die sicherere Variante, damit Assigned nicht in die Falle läuft...
Ich mag Pascal...

wp_xyz
Beiträge: 4889
Registriert: Fr 8. Apr 2011, 09:01

Re: Objekte korrekt freigeben

Beitrag von wp_xyz »

Ich mach das auch so. Allerdings gab's dazu vor längerer Zeit eine Diskussion in der Delphi-Community (http://www.nickhodges.com/post/Using-FreeAndNil.aspx): "In my mind, the answer to the question “When should I use FreeAndNil?” is “never”, or at least “Almost never, and if you must use it, make sure that there is a really, really good reason to do so and that you clearly document that reason” ... If your code requires you to use FreeAndNil to reveal and easily find bugs, then your design is wrong. Good, clean code never feels the need to worry about errant pointers". Der Hintergrund ist, dass, wenn man einen Pointer auf nil setzen muss, um zu signalisieren, dass der Pointer ab jetzt nicht mehr gültig ist, dann hat man ein Design-Problem, weil die Gültigkeit des Pointers weitreichender ist als nötig.

Benutzeravatar
af0815
Lazarusforum e. V.
Beiträge: 6209
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: Objekte korrekt freigeben

Beitrag von af0815 »

Full ACK

Ich arbeite meist im Constructor/Destructor oder umgrenze das mit try/finally.
Blöd kann man ruhig sein, nur zu Helfen muss man sich wissen (oder nachsehen in LazInfos/LazSnippets).

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

Re: Objekte korrekt freigeben

Beitrag von Warf »

Fpc verfügt über referenzzählung, aber nur für (COM) interfaces. Es wäre also problemlos möglich für jedes Objekt ein Interface zu schreiben und nur Zuweisungen über das Interface zu machen, und somit einen referenzzähler zu implementieren

Antworten