Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Speicherfreigabe innerhalb einer Klasse
IhopeonlyReader - Mo 21.04.14 21:01
Titel: Speicherfreigabe innerhalb einer Klasse
Guten Tag,
ich habe jetzt mal ein Server-Client spiel geschrieben.. Server wird pro Client ein paar Byte im Arbeitsspeicher größer, da ich pro Client (in Socket.Data) ein paar Daten ablege, die ich wenn der Client disconnected wieder freigebe...
Leider habe ich, wenn ein Client connected und wieder disconnected 12 Byte (oder sogar KB?) mehr im Arbeitsspeicher..
In der Summe der Clients ist es schnell über 1,5 GB wie ich gemerkt habe :/
ich versuche alles freizzugeben, aber wie soll ich Variablen vom Typ TDatetime oder sontiges freigeben?
Hier mal die Klasse die in die Data des Sockets gehangen werden:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9:
| fName: String; fImSpiel: Boolean; fSpielfeld: TSpielfeld; fDran: Boolean; fGegner: TCustomWinSocket; fFarbe: TBesetzt; fLastSendTime: TDateTime; SSSekTime: TDateTime; SSAnzSend: Cardinal; |
Diese Daten werden auf Connecten des Sockets erstellt:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| PD := TPDaten.Create; PD.fName := ''; PD.fImSpiel := False; PD.fDran := False; PD.fLastSendTime := Now; PD.SSAnzSend := 0; PD.SSSekTime := Now; Socket.Data := PD; |
und wenn er disconnected, gebe ich so frei, leider scheinbar nicht alles:
Delphi-Quelltext
1: 2: 3: 4: 5:
| TPDaten(Socket.Data).fName := ''; Finalize( TPDaten(Socket.Data).fName ); TPDaten(Socket.Data).Free; TPDaten(Socket.Data).fSpielfeld.Free; |
was habe ich vergessen? was muss ich wie freigeben?
Xion - Mo 21.04.14 21:43
Klassen müssen freigegeben werden, Records, Arrays und einfache Variablen (z.B. Integer) nicht. Naiv gesprochen: Alles was du von Hand erzeugst (mit .Create oder new() z.B.) muss auch wieder von Hand freigegeben (mit .Destroy/Free oder dispose() z.B.).
Zu deinem angegebenen Code beim Erzeugen genügt zum Freigeben ein
Delphi-Quelltext
1:
| TPDaten(Socket.Data).Free; |
Die Klasse TPDaten muss sich selbst darum kümmern, dass die eigenen privaten Attribute aufgeräumt werden. Dazu schreibst du einfach für die Klasse einen eigenen Destruktur (override nicht vergessen!)
Delphi-Quelltext
1: 2: 3: 4: 5:
| type TPDaten = class [...] public destructor Destroy; override; end; |
Die Klasse weiß ja selbst am besten, ob TSpielfeld eine Klasse ist und dann fSpielfeld.Free aufgerufen werden muss (oder ob es z.B. nur ein Zeiger ist und das Spielfeld nicht gelöscht werden soll).
Wenn du
FastMM [
http://sourceforge.net/projects/fastmm/] benutzt, sagt er dir beim Beenden des Programmes, wieviele Bytes Speicher geleaked sind und von welchem Typ sie sind (z.B. in deinem Fall TPDaten), was extrem nützlich ist und die beste mir bekannte Methode, mit Speicherlecks zurecht zu kommen. FastMM wurde mir auch mal hier im Forum empfohlen aus andrem Zusammenhang (es ist auch effizienter was Strings angeht), dem kann ich mich nun anschließen ;)
mandras - Mo 21.04.14 21:43
Ohne dein Programm näher zu kennen zwei Anmerkungen:
a) Freemem würde ich gar nicht verwenden (oder hast Du irgendwo GetMem eingesetzt?)
c) Wozu das Finalize? fName ist ein normaler langer String, der wird automatisch freigegeben
b) ich sehe eine Fehlermöglichkeit in der Reihenfolge:
Delphi-Quelltext
1: 2:
| TPDaten(Socket.Data).Free; TPDaten(Socket.Data).fSpielfeld.Free; |
tausche die beiden Zeilen einmal gegeneinander aus, möglicherweise ist das das Problem
(weil laut reiner Lehre Socket.Data nach dem Free bereits eigentlich nicht mehr existiert)
Wie gesagt, meine Vermutungen weil ich die Details nicht kenne.
IhopeonlyReader - Mo 21.04.14 21:50
b) ich hatte das Spielfeld schonmal davor freigegen, aber da es nicht immer exisitert, tritt teilweise ein fehler auf und es springt aus dem try-except block und Data.Free würde nicht mehr aufgerufen werden...
ich bringe fSpielfeld.free nun in das destroy ereignis von TPDaten...
aber darin liegt leider nicht der fehler :/
a) Freemem war auch nur ein versuch, nein getmem benutze ich nicht
c) finalize war auch nur ein Versuch, dispose hatte ich auch versucht :D
Xion - Mo 21.04.14 22:47
IhopeonlyReader hat folgendes geschrieben : |
b) ich hatte das Spielfeld schonmal davor freigegen, aber da es nicht immer exisitert, tritt teilweise ein fehler auf und es springt aus dem try-except block und Data.Free würde nicht mehr aufgerufen werden... |
Also das klingt sehr unsauber und äußerst gefährlich. Du rufst ein .Free auf ein Objekt auf, das nicht existiert? Ok, solange dort nil hinterlegt ist, dann ist es nur unsauber, aber wenn nicht, wer weiß was da alles passieren kann. Im schlimmsten Fall ist dort im Speicher jetzt ein andres Objekt selben Typs und es wird gelöscht. Setze es initial auf nil und teste darauf, bevor du Free aufrufst (bzw. Free prüft selbst schon darauf). Es gibt auch FreeAndNil(obj), womit du gleich den Zeiger noch auf nil setzt.
Was dispose angeht kannst du auch mal hier gucken:
http://www.entwickler-ecke.de/viewtopic.php?t=111119
IhopeonlyReader - Mo 21.04.14 23:10
Leider ist es so,
Wenn 2 Spieler gegeneinander spielen, so wird das Spielfeld erzeugt und bei den Daten beider unter fspielfeld hinterlegt.
Wenn jetzt Spieler 1 und 2 nacheinander disconnecten gibt Spieler 1 das Spielfeld schon frei, Spieler 2 dann unnötiger Weise nochmal
Das fix ich noch, aber darin liegt ja nicht der speicher Fehler an sich...
Und bei einem direkten verbinden und trennen existiert somit kein Spielfeld, darin kann also kein Fehler liegen
jaenicke - Mo 21.04.14 23:54
Und auch, wo der Speicher reserviert wurde inkl. Stacktrace.
IhopeonlyReader - Mo 21.04.14 23:55
Wie muss ich das brnutzen? Habe die ZIP runtergeladen und entpackt.. ( habe delphi 7PE)
Xion - Di 22.04.14 08:03
IhopeonlyReader hat folgendes geschrieben : |
Leider ist es so,
Wenn 2 Spieler gegeneinander spielen, so wird das Spielfeld erzeugt und bei den Daten beider unter fspielfeld hinterlegt.
Wenn jetzt Spieler 1 und 2 nacheinander disconnecten gibt Spieler 1 das Spielfeld schon frei, Spieler 2 dann unnötiger Weise nochmal |
Das verstehe ich nicht ganz. Benutzt du gemeinsamen Speicher dafür? Sitzen die Spieler am selben Rechner? Denn sonst sind es zwei vollständig unterschiedliche Objekte. Der Client hat normalerweise eine Kopie der Daten des Severs. Es gibt also zwei unabhängige Spielfelder, die du auch beide freigeben musst.
PS: Da mag ich auch etwas daneben liegen, weil ich nicht ganz sicher bin was Socket.Data eigentlich genau macht. Hab aber grad auch keine Zeit das nachzuschlagen ;)
IhopeonlyReader hat folgendes geschrieben : |
Wie muss ich das brnutzen? Habe die ZIP runtergeladen und entpackt.. ( habe delphi 7PE) |
Du musst nur die FastMM4.pas in deinem Projekt hinzufügen und in die Uses des Projekts (mit F8 zu finden) ganz oben als aller erste Unit einbinden.
IhopeonlyReader - Di 22.04.14 11:01
Xion hat folgendes geschrieben : |
Benutzt du gemeinsamen Speicher dafür? Sitzen die Spieler am selben Rechner? Denn sonst sind es zwei vollständig unterschiedliche Objekte. Der Client hat normalerweise eine Kopie der Daten des Severs. Es gibt also zwei unabhängige Spielfelder, die du auch beide freigeben musst. |
Socket.Data ist ein Pointer, hier habe ich die Klasse TPDaten hinterlegt... (Sieht man gut im OnConnect event (siehe Post 1)
TPDaten enthält wiederrum eine variable vom Typ TSpielfeld, da TSpielfeld auch eine Klasse ist, ist fSpielfeld eigentlich nur ein Pointer.
Wenn jetzt Spieler A gegen Spieler B spielt, so wird am Spielstart im Server ein neues Spielfeld (vom Typ TSpielfeld) erzeugt und sowohl bei Spielera in das fSpielfeld als auch bei Spieler B in das fSpielfeld gesetzt... so besitzen beide Spieler in ihren TPDaten ein Pointer auf das Spielfeld.
IhopeonlyReader - Di 22.04.14 11:53
So, der Fehler scheint dank FastMM4 behoben zu sein...
Fehler 1: Beim erstellen wurde ein Stream erstellt, der nicht freigegeben wurde da er mehrmals kopiert wurde.. (nicht im gepostet quelltext zu sehen)
Fehler 2: Beim schließen der Anwendung, wenn noch Clients verbunden waren, wurden diese nicht beendet sondern die Anwendung einfach geschlossen.. nun werden die Verbindungen vorher ordnungsgemäßg beendet und somit auch ihre Daten freigegeben
Ob mein server nun einem DDos angriff ohne Arbeitsspeicherüberlauf aushält wird noch getestet :D
Allerdings noch ein paar Fragen dazu am Rande...:
a) Wenn etwas nicht zwansläufig existiert (z.B. fSpielfeld vom Typ TSpielfeld und es nicht nil ist, sondern noch ein "veralteter" Wert drin steht, der woanders freigegeben wurde, wie überprüüfe ich ob fspielfeld jetzt noch freigegebn werden muss oder nicht?
aktuell mache ich es so:
Delphi-Quelltext
1: 2: 3: 4:
| try fSpielfeld.free; except end; |
so schmeißt free zwar einen fehler, aber der ist ja egal, da er "abgefangen" wird...
folgendes habe ich probiert:
Delphi-Quelltext
1: 2:
| if (fSpielfeld<>nil) and Assigned(fSpielfeld) then fSpielfeld.Free; |
klappt nur leider nicht :/
Nersgatt - Di 22.04.14 11:54
Benutzte statt FSpielfeld.Free einfach FreeAndNil(FSpielfeld).
Dann funktioniert der Test mit Assigned(FSpielfeld) auch zuverlässig.
IhopeonlyReader - Di 22.04.14 12:10
naja, es wird ja nicht die variable fSpielfeld zum feigeben verwendet!
beispiel
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| var Spielfeld1, Spielfeld2, DASSpielfeld: TSpielfeld; begin DasSpielfeld := TSpielfeld.create; Spielfeld1 := DAsSpielfeld; Spielfeld2 := DASSpielfeld;
if Random(2)=1 then Spielfeld2.Free; if Assigend( Spielfeld1 ) then Spielfeld1.Free; |
jaenicke - Di 22.04.14 13:08
Benutze ein Interface und arbeite dann ausschließlich mit diesem Interface. Dann gibt Delphi dieses automatisch erst frei, wenn die letzte Referenz darauf weg ist. (Wenn das das ist, was du möchtest.)
Nersgatt - Di 22.04.14 13:10
Nach Deinem Beispielquellcode zeigen DasSpielfeld, Spielfeld1 und Spielfeld2 alle auf die selbe Instanz.
Daher ist es egal, ob Du nun DasSpielfeld, Spielfeld1 oder Spielfeld2 freigibst. Es gibt immer die selbe Instanz frei.
IhopeonlyReader - Di 22.04.14 13:15
ja Nersgatt, das ist es ja.. sie sollen alle auf die selbe instanz zeigen!
Allerdings wird das spielfeld freigegeben, sobald 1 spieler disconnected (da bei einem 1 vs 1) das spielfeld nicht mehr benötigt wird.
Wenn der andere dann auch disconnected, dann will er das spielfeld auch freigeben, obwohl es schon freigegeben ist !
mir geht es nun allgemein um das beispiel, wie ich herausfinde ob ein pointer auf eine freigegebene instanz zeigt.
Nersgatt - Di 22.04.14 13:22
IhopeonlyReader hat folgendes geschrieben : |
mir geht es nun allgemein um das beispiel, wie ich herausfinde ob ein pointer auf eine freigegebene instanz zeigt. |
Genau wie ich sagte. Verwende FreeAndNil. Damit zeigen hinterher alle Pointer auf nil (die Pointer zeigen ja alle auf die selbe Stelle).
IhopeonlyReader - Di 22.04.14 13:31
wirklich?
ich dachte free and nil würde das machen
fSpielfeld1.Free;
fSpielfeld1 := nil;
weil dann wäre ja nur spielfeld 1 nil und spielfeld2 weiterhin auf die instanz gerichtet, die aber durch free freigegeben wurde
Nersgatt - Di 22.04.14 13:35
Mea culpa. Du hast Recht. Ich nehm alles zurück und behaupte das Gegenteil :hair:
Xion - Di 22.04.14 18:02
Da fallen mir mehrere Möglichkeiten ein:
:arrow: Du benutzt einen Zeiger auf den Zeiger. Dann steckt hinter dem zeiger entweder das Objekt oder nil. Alle Zeiger zeigen dann auf das nil.
:arrow: Du machst ein Record mit Spielfeld und SpielfeldExists. Darauf hast du deine Pointer (das ist so ungefähr das selbe wie der vorherige Punkt ;) )
:arrow: Du machst deine Klasse als Singleton und benutzt eine class function zum Test, ob die Instanz existiert. Dann kannst du aber immer nur ein Spielfeld haben!
IhopeonlyReader - Mi 23.04.14 12:06
ich mache das fast so wie du sagst Xion...
ich habe ja sowohl den gegnersockert als auch das spielfeld als pointer...
somit mache ich es so
Delphi-Quelltext
1: 2: 3: 4: 5: 6:
| -> Spieler.Gegner.Spielfeld := nil; -> Spieler.Spielfeld.Free; -> Spieler.Spielfeld := nil; |
Xion - Mi 23.04.14 14:56
Du könntest es in etwa so machen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20:
| type TSpielfeld = class end; type PSpielfeld = ^TSpielfeld;
type TSpieler = record Spielfeld: ^PSpielfeld; end;
procedure RunTest; var spieler1, spieler2: TSpieler; ptrSpielfeld: PSpielfeld; begin Spielfeld^ := TSpielfeld.Create; spieler1.Spielfeld := @ptrSpielfeld; spieler2.Spielfeld := @ptrSpielfeld; ptrSpielfeld^.Free; ptrSpielfeld:= nil; if spieler1.Spielfeld^ <> nil then ShowMessage('Dieser Code funktioniert nicht wie Xion sich das vorstellt!'); end; |
Nicht vergessen: ptrSpielfeld wird ungültig bei Verlassen der Funktion, dies ist nur ein Test und wenn du das so einsetzen willst muss da der Pointer "global" hinterlegt werden. Man könnte ihn sogar ins Spielfeld integrieren und eine Funktion "getReference" oder so dafür schreiben. Ganz wie man möchte.
jaenicke - Do 24.04.14 06:17
In der ersten Zeile meinst du vermutlich ptrSpielfeld^ und nicht Spielfeld^, oder?
Trotzdem ist das falsch. Denn das Ziel des Pointers ist nicht initialisiert, so dass da einfach irgendwelcher Speicher überschrieben wird...
Der Speicher muss in so einem Fall explizit, z.B. mit GetMem, initialisiert werden oder eine Variable als Ziel benutzt werden, auf die der Pointer zeigt (ich vermute mal du hattest letzteres im Sinn).
Die ganze Pointerspielerei halte ich aber ohnehin für wenig sinnvoll. Mit Interfaces kann man das auch erreichen. Wenn man das Spielfeld wirklich gezielt zerstören möchte, kann man ein Interface nutzen, in dem der Pointer liegt.
Viel sinnvoller ist aber vermutlich das Spielfeld einfach direkt als Pointer umzusetzen, damit jeder darauf so lange zugreifen kann wie er es braucht. Selbst wenn die visuelle Darstellung schon nicht mehr existiert, kann das Spielfeld dahinter ja noch weiter leben um z.B. die Endstände noch abrufen zu können.
Xion - Do 24.04.14 13:34
Oh, danke für die Korrektur. :oops:
Habs eben mal ausprobiert, es war viel zu kompliziert gedacht:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
| var spieler1, spieler2: TSpieler; ptrSpielfeld: PSpielfeld; Spielfeld: TSpielfeld; begin Spielfeld := TSpielfeld.Create; spieler1.Spielfeld := @Spielfeld; spieler2.Spielfeld := @Spielfeld; Spielfeld.Free; Spielfeld := nil; if spieler1.Spielfeld^ = nil then ShowMessage('Dieser Code funktioniert wie Xion sich das vorstellt!'); end; |
Hinter Spielfeld verbirgt sich natürlich bereits ein Pointer auf die eigentliche Struktur (deswegen ja auch call by reference, wenn man eine Klasse als Parameter übergibt). Sollte jetzt aber so passen, solange die variable Spielfeld global verwaltet wird.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!