Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Speicherleck bei anonymem Objekt - wie vorgehen?


Stephan.Woebbeking - Mi 03.08.11 10:56
Titel: Speicherleck bei anonymem Objekt - wie vorgehen?
Hallo *.

Nachdem ich mir da bisher keine Gedanken drum gemacht hab (und auch keine Probleme hatte), habe ich jetzt angefangen meine Programme mal etwas auf Speicherlecks zu untersuchen. Als langjähriger Java Befürworter- und Programmierer könnt ihr Euch ausdenken wie das aussieht - der Carbage Collector ist spätestens jetzt mein Freund. ;)

Konkrete Frage: Konstrukte mit anonymen Objekten wie diese finden sich in Java zu Hauf und auch in Delphi macht es gelegentlich viel Sinn:

Delphi-Quelltext
1:
  if ( receiver.GetReceiverRange.IsInRange( TransmitterList.CurrentTransFreq ) ) then                    


Die Methode "GetReceiverRange" erzeugt ein Objekt. Da ich es nicht zuweise kann ich auch kein "Free" aufrufen, bekomme also ein Speicherleck. Wie würde man vorgehen, die einzige Möglichkeit die ich so sehe ist, die Referenz aufzuheben und dann eben ein "Free" zu machen. Gibt's noch was anderes?

Danke,
Stephan


Narses - Mi 03.08.11 12:08

Moin!

user profile iconStephan.Woebbeking hat folgendes geschrieben Zum zitierten Posting springen:
Wie würde man vorgehen, die einzige Möglichkeit die ich so sehe ist, die Referenz aufzuheben und dann eben ein "Free" zu machen. Gibt's noch was anderes?
Ganz ohne "Aufheben" geht in Delphi nicht, das ist klar. :| Du kannst aber mit einem böhsenwith-Block implizit "aufheben":

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
with receiver.GetReceiverRange do try
  if IsInRange( TransmitterList.CurrentTransFreq ) then
    //...
finally
  Free;
end;
Ob das jetzt besser ist... ? :nixweiss:

cu
Narses


uall@ogc - Mi 03.08.11 12:10

Naja eigentlich ist ja die Range am Recevier gekoppelt, dann hätte man es so aufbauen sollen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
type TReceiver = class(Tobject)
private
FReceiverRange: TReceiverRange;
public
  property Range: TReceiverRange read FReceiverRange;
end;


und im TReceiver die Range erstellen/freigeben. So wäre es sauber gelöst. Ansonsten helfen dir eventl. Interfaces.


Lemmy - Mi 03.08.11 12:56

Hi,

ich sehe da noch kein zwingendes Speicherleck, denn hier wäre dann "receiver" für die Freigabe verantwortlich - schließlich wird die Instanz auch von Receiver erzeugt. Und da hättest Du dann div. Möglichkeiten.

Sollte GetReveiverRange eine Anforderung eines Singletons sein, dann hättest Du immer noch im Finalization Bereich der unit die Möglichkeit die Instanz wieder freizugeben

Grüße


Stephan.Woebbeking - Mi 03.08.11 13:15

Ok, an der Stelle fällt mir auf, dass ich euch eine Information vorenthalten habe (nicht absichtlich): Das Rückgabeobjekt wird in der Prozedur erzeugt, damit der Empfänger mit irgendwelchen Berechnungen auf dem Rückgabeobjekt (das kommt häufiger vor) keine Seiteneffekte im Receiver erzeugt. Darauf aufbauend wäre es vielleicht richtig, dem Receiver bereits ein Objekt zu geben, das könnte ich dann in der rufenden Methode auch wieder freigeben. Aber der (Schreib-) Aufwand ist deutlich höher; natürlich habe ich dann kein anonymes Objekt mehr, d.h. ich kann es auch freigeben. Aber lohnt sich der zusätzliche Aufwand dann wirklich?


jaenicke - Mi 03.08.11 13:44

Ein Objekt zu erzeugen und in einer Funktion zurückzugeben ohne sich weiter darum zu kümmern, ist einfach nur unsauberer Code durch ein schlechtes Konzept.

Aber wenn du das so unbedingt machen möchtest, kannst du ja wie user profile iconDaniel schon geschrieben hat, Interfaces verwenden. Das Objekt wird dann automatisch freigegeben, wenn alle Referenzen darauf aus dem Scope sind oder explizit auf nil gesetzt wurden.


Stephan.Woebbeking - Mi 03.08.11 14:40

Hm, also direkt so kann ich das nicht umsetzen. Denn damit verteile ich ja jedesmal DASSELBE Objekt, was ich eigentlich tue ist, jedesmal eine KOPIE anzufertigen, damit jeder seine eigene Instanz bekommt.

Noch eine andere Frage:
Nachdem ich erstmal etliches geradegezogen habe, bekomme ich noch einen TIdThreadSafeInteger und zwei TIdCriticalSection gemeldet. Ich bin mir aber keiner Schuld bewusst?


jaenicke - Mi 03.08.11 15:37

user profile iconStephan.Woebbeking hat folgendes geschrieben Zum zitierten Posting springen:
Hm, also direkt so kann ich das nicht umsetzen. Denn damit verteile ich ja jedesmal DASSELBE Objekt, was ich eigentlich tue ist, jedesmal eine KOPIE anzufertigen, damit jeder seine eigene Instanz bekommt.
Genau das kannst du mit Interfaces ja machen. Denn der Code für die Freigabe durch Referenzzählung wird dann automatisch generiert.

user profile iconStephan.Woebbeking hat folgendes geschrieben Zum zitierten Posting springen:
Noch eine andere Frage:
Nachdem ich erstmal etliches geradegezogen habe, bekomme ich noch einen TIdThreadSafeInteger und zwei TIdCriticalSection gemeldet. Ich bin mir aber keiner Schuld bewusst?
Aktualisiere mal deine Indyversion. ;-)


Stephan.Woebbeking - Do 04.08.11 11:41

Wow, Interfaces sind 'ne klasse Sache - in Java, in Delphi war mein Stand immer noch, dass es das dort nicht gibt... Man lernt doch immer noch dazu (nicht jeder, aber lass uns das nicht vertiefen... ;) ).

Ok, jetzt habe ich die Funktion, die mein Objekt erzeugt und dann zurückgibt in ein Interface gepackt (von IInterface abgeleitet) und meine eigentliche Klasse von TInterfacedObject abgleitet. Dann habe ich noch das .Free; in der rufenden Funktion entfernt. Die übergebene Instanz wird aber jetzt wieder als Speicherleck identifiziert. Muss ich sonst noch was ein/umstellen?

Stephan


jaenicke - Do 04.08.11 12:36

Die Klasse, um die es geht, musst du von TInterfacedObject ableiten und ein Interface zusätzlich erstellen, das deine Klasse implementiert. Zurückliefern tust du dann nur das Interface.

Die Klasse, in der du die Funktion liegt, die das zurückgibt, braucht kein Interface.


Stephan.Woebbeking - Do 04.08.11 13:05

Ich muss gestehen, das habe ich jetzt nicht wirklich verstanden... Lass mich mal kurz die Ist-Situation skizzieren (kein vollständiges Program, nur prinzipiell):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
interface

class ValueWrapper
   value: type;
end;

class Provider
   function GetValue: ValueWrapper;
end;

class User
   function DoSomething;
end;

var
   providerInstance: Provider;

implementation

Provider.GetValue;
begin
   Result := ValueWrapper.Create();
   Result.value := ...;
end;

User.DoSomething;
var
   val: ValueWrapper;
begin
   val := providerInstance.GetValue;
   ... := val.value; // Das ist ja ganz unproblematisch
   val.value := val.value + 1// Wenn ich hier nicht andere "User" verwirren will, dann muss ich jedem seine eigene Instanz geben, richtig? Das ist der ganze Aufhänger...
end;


Wenn ich jetzt nur ein Interface zurückliefern würde, habe ich doch keinen Wert den ich verwenden und auch ändern kann?

Stephan


jaenicke - Do 04.08.11 13:48


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
type
  ITrash = interface
    function GetTest: string;
    procedure SetTest(const Value: string);
    property Test: string read GetTest write setTest;
  end;

  TTrash = class(TInterfacedObject, IInterface)
  private
    FTest: string;
    function GetTest: string;
    procedure SetTest(const Value: string);
  public
    property Test: string read GetTest write SetTest;
  end;

  TProvider = class
  public
    function GetTrash: ITrash;
  end;

function TTrash.GetTest: string;
begin
  Result := FTest;
end;

procedure TTrash.SetTest(const Value: string);
begin
  FTest := Value;
end;

function TProvider.GetTrash: ITrash;
begin
  Result := TTrash.Create;
  Result.Test := 'abc';
end;
Ungetestet, sollte so klappen.


Kha - Fr 05.08.11 11:13

Kurzer Einwurf von einem mittlerweile eher außerhalb von Delphi stehenden: Es ist doch aber immer noch nicht die Delphi-Norm, in der Art Interfaces nur um des Freigebns willen einzuführen, oder? Ich denke nicht, dass man so um die Sprache herumprogrammieren sollte.


jaenicke - Fr 05.08.11 11:47

Wenn man diese Funktion braucht, bleibt gar nichts anderes übrig. ;-)


Kha - Fr 05.08.11 16:24

Aus Bequemlichkeit wollen ja, aber etwas brauchen, was Delphi eigentlich gar nicht vorsieht :nixweiss: ? In Delphi ist es nunmal so gedacht, dass man sich um das Speichermanagement von Klassen selbst kümmern muss, da ist doch ein "hmja, eigentlich haben wir Interfaces nur wegen COM eingeführt"-Artefakt kein Ausweg.


jaenicke - Fr 05.08.11 18:08

Ich mache es auch eher umgekehrt, ich schalte durch entsprechende Implementierung der Referenzzählungsmethoden selbige aus. Damit ich mich selbst darum kümmern kann.

Sauberer wäre es schon mit einem anderen Konzept. Trotzdem funktioniert es so auch gut.

Eine andere Möglichkeit wären eventuell auch Records als Rückgabewerte. Dann übernimmt Delphi auch die Speicherverwaltung. Da Records auch Konstruktoren und Methoden beinhalten können, sind diese in einem Fall wie diesem oft sehr sinnvoll.


Martok - So 07.08.11 02:01

user profile iconjaenicke hat folgendes geschrieben Zum zitierten Posting springen:
Eine andere Möglichkeit wären eventuell auch Records als Rückgabewerte. Dann übernimmt Delphi auch die Speicherverwaltung. Da Records auch Konstruktoren und Methoden beinhalten können, sind diese in einem Fall wie diesem oft sehr sinnvoll.
Und für ältere Delphis kann man auch object verwenden. Verhält sich wie Records in XE, werden allerdings auf dem Stack alloziiert. Also nicht zu groß machen ;)
Hatte ich so verwendet im Bot für die eeBotLiga, und es war ordentlich schnell.


Ich verwende Interfaces eigentlich nur für die Referenzzählung. Da gabs mal irgendwann einen Thread [http://www.delphi-forum.de/viewtopic.php?t=101139] zu, in dem user profile iconjaenicke z.B. Container-Objekte vorgeschlagen hat. Dann muss man sich nicht weiter damit beschäftigen, dass Properties sehr unpraktisch werden, sondern kann einfach die Objekte verwenden wie gedacht.

Sind aber alles Krücken, ja. Ohne im Compiler rumzuhacken wird das nicht sauber was.

Einigermaßen ordentlich implementiert hat Borland das im Notification-System von TComponent. Das basiert aber eben darauf, dass man an jeder stelle selbst die entsprechenden Notifications versenden muss. Hat natürlich nichts mit dem eigentlichen Ziel "schönes Chaining" zu tun.

[persönlicherEindruck]Um schön zu programmieren ist Delphi einfach nicht die richtige Sprache; es ist eher produktiv aber dafür oft hässlich.[/persönlicherEindruck]


jaenicke - So 07.08.11 07:32

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:
Ich verwende Interfaces eigentlich nur für die Referenzzählung.
Ich benutze die durchaus auch für deren ursprünglichen Zweck. :mrgreen:
Die sind auch für reine Delphiprogramme dabei nützlich, denn auf diese Weise kannst du z.B. ein Objekt als von IUnknown abgeleitetes Interface an eine DLL übergeben. (Und hast als Bonus die Möglichkeit die DLL auch in anderen Sprachen zu nutzen.)

object würde ich aber nicht mehr verwenden, das gibt es nur für Kompatibilität mit Turbo Pascal noch. ;-) Seit Delphi 2006 hat man ja auch in Records ähnliche Funktionalität und noch mehr.


dummzeuch - Sa 13.08.11 11:00

Hi,

user profile iconMartok hat folgendes geschrieben Zum zitierten Posting springen:

Und für ältere Delphis kann man auch object verwenden.


Wobei "aeltere Delphis" bedeutet: Delphi 1 bis XE (zu XE2 kann ich noch nichts sagen, aber es wuerde mich nicht wundern, wenn selbst die 64 Bit Version noch compatibel dazu waere).

twm


jaenicke - Sa 13.08.11 11:37

Dass es das noch gibt, heißt aber nicht, dass man es verwenden sollte. Das ist wie mit WinExec oder TThread.Resume, das gibt es auch noch, sollte aber ebenfalls nicht mehr verwendet werden.


Martok - Sa 13.08.11 18:54

user profile icondummzeuch hat folgendes geschrieben Zum zitierten Posting springen:
Wobei "aeltere Delphis" bedeutet: Delphi 1 bis XE (zu XE2 kann ich noch nichts sagen, aber es wuerde mich nicht wundern, wenn selbst die 64 Bit Version noch compatibel dazu waere).
Ja, genau.

Ich wöllte das gerne gelesen haben als "es ist rückwärtskompatibel, Record-Methoden sind das nicht".

Übrigens: der generierte Code ist exakt der gleiche (Getestet: D7,D10). MethodenRecord ist also nur eine neue Syntax für object. Es würde mich nicht wundern, wenn das von Delphi intern auf eine einheitliche Form abgebildet wird.


BenBE - Mi 17.08.11 14:53

Prinzipiell sind Objects von der Speicherverwaltung mit den Klassen in C++ vergleichbar vom Layout. Möchte man Cross Language haben, ist man mit COM/DCOM und anderen Schnittstellen aber oft besser bedient.


Stephan.Woebbeking - Di 22.05.12 10:48

Oha, schon lange her, lange nicht reingeschaut... Soweit ich das für mich zusammenfassen kann, ist so etwas schönes wie der Garbage Collector unter Java nicht mit Bordmitteln in Delphi erreichbar. Interfaces sind in dedizierten Situationen eine Alternative, aber klassisches, eigenes Memory Management ist State-of-the-Art, habe ich das soweit richtig verstanden? Damit wird auch die an sich sehr bequeme Vorgehensweise aus einer Funktion eine Instanz zurück zu liefern etwas bedenklich, weil der Rufer ja vielleicht nicht dran denkt die Instanz freizugeben - er hat sie ja nicht angelegt.

Das ist für mich die Essenz, wenn ich falsch liege, dürft ihr mich natürlich gern korrigieren, sonst schließe ich das Thema in den nächsten Tagen.

Danke, Stephan


Delete - Di 22.05.12 11:02

Vielleicht sind auch Smartpointers passend an dieser Stelle:
http://members.adug.org.au/2011/12/05/smart-pointers/


Stephan.Woebbeking - Mi 23.05.12 08:10

Sieht definitiv interessant aus! Allerdings durchblicke ich es nicht soweit, dass ich - ohne es erstmal anzuwenden - die Grenzen verstehen würde. Nutzt du das? Was sind die Nachteile? Der zusätzliche Implementierungaufwand ist ja nicht so groß, soweit ich das sehen kann, oder? Man muss halt bloß dran denken, beim Anlegen der Instanzen die veränderte Form zu nutzen. Den Beitrag von Stefan Meisner finde ich auch interessant, leider ist da nur ein Teil des Mechanismus abgebildet soweit ich das sehen kann.

vG,
Stephan


Delete - Mi 23.05.12 08:17

Ja, ich nutze das. Ich bevorzuge den ObjectGuard, da ich das andere z.Zt. Overkill finde
Ein Beispiel:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  sl: TStringList
begin
  TObjectGuard.Guard(sl, TStringList.Create);
  sl.add('123');
  sl.add('456');
end;  // hier wird scope verlassen (=> .free), da IInterface.  keine try-finallys

Ich nutz das sogar fast täglich...
Solche Dinge hab ich mir auch für BeginUpdate und EndUpdate gebaut, falls Du daran interessiert bist.


uall@ogc - Mi 23.05.12 12:10

Was ist denn der Sinn von dem Guard? Der funktioniert doch nur innerhalb der gleichen Funktion, dort würde doch auch ein try/finally reichen. Wenn man Guard wie in der Funktion vom Threadersteller als Rückgabewert verwendet (TObjectGuard.Guard(Result, TStringList.Create)), knallt es doch spätestens beim Zugriff der aufrufenden Funktion, da das Objetc wieder freigegeben wurde. Oder hab ich das jetzt falsch verstanden?


Delete - Mi 23.05.12 12:12

Ja. Der Scope geht bis zum "end"


uall@ogc - Mi 23.05.12 12:20

Verwendet man bei der Implementation eines Object ein Interface dann kann das Interface auch als Rückgabeparameter verwendet werden und ist ausserhalb der Funktion verwendbar. Der ObjectGuard löst dieses Problem nicht (da dort die StringList und der Guard als Rueckgabe weitergegeben werden muesste), es ist somit keine Lösung auf die Ausgangsfragestellung von Stephan (Object als Rückgabewert). Zumal sowas dann bei einem "Mischmasch" einfach nur total unübersichtlich wird (muss ich die StringList nun freigeben oder nicht?) was bei einer IInterface Implementierung des Objektes eindeutig zu erkennen ist. Welchen Vorteil hat man also, ausser einer Zeile die man sich beim der Programmierung einspart? (oder 3 mit Try/Finally?)