Delphi-PRAXiS
Seite 1 von 2  1 2   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Automatische Referenzzählug bei Interfaces (https://www.delphipraxis.net/208845-automatische-referenzzaehlug-bei-interfaces.html)

nb78 21. Sep 2021 10:38

Delphi-Version: 10.4 Sydney

Automatische Referenzzählug bei Interfaces
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

ich wollte alten Code umstellen und ein Interface implementieren anstatt überall im Code die Klassenimplementierung zu kennen. Die Umstellung klappte recht gut und alle Unit Tests laufen erfolgreich durch. Als ich dann aber die Speichercheck Option angeschaltet habe kam es bei einem Testfall zu nicht wieder freigegebenen Speicher. Benutze ich die Klassen entsteht kein Speicherproblem. Die Klasse implementiert "TInterfacedObject" und daher sollte sich ja Delphi um das Aufräumen des Speichers kümmern. Das klappt auch in fast allen Tests.

Der Code ist ein Formelparser der dann die Ableitung bilden kann. Bei "3*r^2" kommt es allerdings zum Speicherleck, da hier die Ableitungsregeln für die Multiplikation und Potenzen angewendet werden.

Für die Ableitung wird eine Formel-Instanz nach den Regeln erstellt.

Multiplikation
Code:
  var newOp1:=TMultiplyTerm.create(fop1.getDerivative, fop2.copy);
  var newOp2:=TMultiplyTerm.create(fop1.copy, fop2.getDerivative);
  var temp:=TAddTerm.create(newop1.simplify, newop2.simplify);
  result:=temp.simplify;
{$IFDEF TERM_INTERFACE}
  newop1:=nil;
  newop2:=nil;
  temp:=nil;
{$ELSE}
  newop1.Free;
  newop2.Free;
  temp.Free;
{$ENDIF}
Potenz
Code:
   
    var newop2:=TPowerTerm.create(getFirstOperand.copy, TConstantTerm.create(getSecondOperand.getValue(0)-1));
    var temp:=TMultiplyTerm.create(getSecondOperand.copy, newop2.simplify);
    result:=temp.simplify;
{$IFDEF TERM_INTERFACE}
    newop2:=nil;
    temp:=nil;
{$ELSE}
    newop2.free;
    temp.Free;
{$ENDIF}
Laut meinem Verständnis ist ja das setzen auf NIL von Hand unnötig. Es hat in diesem Testfall auch keinen Effekt.
Simplify, Copy und getDerivative erstellen jeweils eine neue Instanz.

Irgendwie scheint die Referenzählung bei der Komplexität durcheinander zu kommen. Hat jemand evtl. eine Idee?

Ich habe auch ein TestTool erstellt welches bei jedem Create und Destroy eine Logausgabe erstellt.

Vielen Dank im Vorraus
Nick Bierwisch

stahli 21. Sep 2021 11:14

AW: Automatische Referenzzählug bei Interfaces
 
Drei grundsätzliche Punkte will ich schon mal nennen:

1) Ist newOp1 eine Interfacevariable? Davon gehe ich nicht aus. Du wirst sie als MyIntVar: IMyIntf deklarieren müssen.

2) Man sollte Interface-Parameter nie als ".Create" übergeben.
Statt
Delphi-Quellcode:
MyProc(TMyIntf.Create);
also besser
Delphi-Quellcode:
var
tmpIntf: IMyIntf;
...
tmpIntf := TMyIntf.Create;
MyProc(tmpIntf);
Sonst kann (wird?) es Probleme mit der Referenzzählung geben.

3) Die ifdef-Stellen würde ich vermeiden. Ich habe jetzt kein Delphi zu Hand, finde das aber verwirrend.
Ich würde eher eine Projektkopie erstellen und dort auf Interfaces umstellen. Deine Klasse(n) dürfen nirgends benutzt werden, außer einmalig das Create. Ansonsten IMMER nur Interfacevariablen verwenden.
Das Create kann man in einer Factory verstecken, die direkt fertige Interfaces herausgibt. Nur sie muss dann (im Implementationsteil) die eigentlichen Klassen kennen.

Der schöne Günther 21. Sep 2021 11:17

AW: Automatische Referenzzählug bei Interfaces
 
Liste der Anhänge anzeigen (Anzahl: 1)
Du sagst die Unit Tests laufen durch. Wo sind die? Ich dachte jetzt kommt ein Testprojekt, und keine Oberfläche zum Klicken. Vielleicht verstehen wir beide unter "Unit Tests" ja auch was anderes.

Die Speicherlecks kann man relativ einfach finden:
  1. Holst du dir die "volle" Version des Speichermanagers FastMM, nicht die abgespeckte die bei Delphi dabei ist: https://github.com/pleriche/FastMM4/releases/
  2. Wirfst du, wie in der Readme beschrieben, die FastMM-Dateien in dein Projektverzeichnis, aktivierst den "Full Debug Mode" und fügst "FastMM4" als ERSTE Unit in dein Projekt ein (die .dpr-Datei)
  3. Beim Beenden des Programms erstellt FastMM dir eine Text-Datei mit allen Speicherlecks und dem Aufrufstack als das Objekt erstellt wurde

Als Beispiel habe ich das Ergebnis mal im Anhang mitgeliefert.

Der schöne Günther 21. Sep 2021 11:21

AW: Automatische Referenzzählug bei Interfaces
 
Ohne mir den Quellcode jetzt genauer angesehen zu haben wären mein spontaner Verdacht zirkuläre Referenzen: Angenommen ein ITerm
Delphi-Quellcode:
a
verweist auf einen Term
Delphi-Quellcode:
b
und der verweist auch wieder auf
Delphi-Quellcode:
a
.

Angenommen in deinem Code interessiert sich mittlerweile niemand mehr für
Delphi-Quellcode:
a
oder
Delphi-Quellcode:
b
bekommen beide doch niemals ihren Referenzzähler auf Null und werden somit nie gelöscht.

Es gibt verschiedene Auswege hieraus, mehr oder weniger aufwändig. Garbage-Collection-Systeme haben solche Probleme nicht.

TiGü 21. Sep 2021 12:30

AW: Automatische Referenzzählug bei Interfaces
 
Überall wo du mit inline variables arbeitest und sowas schreibst:
Delphi-Quellcode:
var newOp1:=TBlablaTerm.create(...);
musst das das auflösen.

Am Besten ganz klassisch mit Variablen-Block oben:

Delphi-Quellcode:
var
  newOp1: ITerm;
begin
  newOp1 := TBlablaTerm.create(...);
Dann klappt es auch mit dem automatischen Referenzzählen.

Begründung: Wenn du
Delphi-Quellcode:
var newOp1:=TBlablaTerm.create
aufrufst, wird eine Instanz von TBlablaTerm erstellt und keine "Interface-Instanz" vom Typ ITerm.

TiGü 21. Sep 2021 12:34

AW: Automatische Referenzzählug bei Interfaces
 
Beispiele (Formatierung teilweise anders):

Delphi-Quellcode:
procedure TForm1.Button1Click(Sender: TObject);

{$IFDEF TERM_INTERFACE}
    function createFormula: ITerm;
    var
        op1, op2, power, multOP1: ITerm;
{$ELSE}
    function createFormula: TTerm;
{$ENDIF}
    begin
        op1 := TVariableRTerm.create;
        op2 := TConstantTerm.create(2);
        power := TPowerTerm.create(op1, op2);
        multOP1 := TConstantTerm.create(3);
        result := TMultiplyTerm.create(multOP1, power);
    end;

    function getDerivativeAt(xvalue: double): double;
    var
        term, d: ITerm;
    begin
        term := createFormula;
        d := term.getderivative;
        Result := d.getValue(abs(xvalue));
{$IFDEF TERM_INTERFACE}
        // term:=nil;
        // d:=nil;
{$ELSE}
        term.free;
        d.Free;
{$ENDIF}
    end;
...
Delphi-Quellcode:
{$IFDEF TERM_INTERFACE}
function TMultiplyTerm.getDerivative:ITerm;
var
    newOp1, newOp2, temp: ITerm;
{$ELSE}
function TMultiplyTerm.getDerivative:TTerm;
{$ENDIF}
begin
  newOp1:=TMultiplyTerm.create(fop1.getDerivative, fop2.copy);
  newOp2:=TMultiplyTerm.create(fop1.copy, fop2.getDerivative);
  temp:=TAddTerm.create(newop1.simplify, newop2.simplify);
  result:=temp.simplify;
...
Delphi-Quellcode:
{$IFDEF TERM_INTERFACE}
function TPowerTerm.getDerivative:ITerm;
var
    newop2: ITerm;
    temp: ITerm;
{$ELSE}
function TPowerTerm.getDerivative:TTerm;
{$ENDIF}
begin
  if getSecondOperand.isConstant then
  begin
    newop2:=TPowerTerm.create(getFirstOperand.copy, TConstantTerm.create(getSecondOperand.getValue(0)-1));
    temp:=TMultiplyTerm.create(getSecondOperand.copy, newop2.simplify);
    result:=temp.simplify;
...

jaenicke 21. Sep 2021 12:50

AW: Automatische Referenzzählug bei Interfaces
 
Das ist übrigens ein gutes Beispiel, weshalb inline Variablen (und dann noch mit Type Inference) oft keine gute Idee sind. Nur weil neue Delphiversionen das (leider) können, muss man es ja nicht überall nutzen...

nb78 21. Sep 2021 16:13

AW: Automatische Referenzzählug bei Interfaces
 
Vielen Dank für die schnellen Antworten.

Das mit den Inline Variablen war mir so nicht bewußt. Ich werde es zukünftig weniger nutzen. In diesem Beispiel ist es egal aber ich nutze es gerne innerhalb von Schleifen oder Blöcken damit die Variable nur lokal nutzbar ist und nicht woanders nochmal genutzt werden kann.
Ich bin davon ausgegangen das Delphi dann den Speicher beim Verlassen des Blocks oder der Schleife automatisch freigibt. Das würde ja die "try finally free" Konstrukte sparen.

nb78 21. Sep 2021 16:16

AW: Automatische Referenzzählug bei Interfaces
 
@Günther
Ich hatte nur den minimalen Code für den Fehlerfall hochgeladen. Ansonsten hätte ich noch die ganze Umgebung inklusive Parser usw. auch liefern müssen. Das waäre dann viel Code gewesen der ja für die Lösung nicht notwendig war.

Den vollen Speichermanager werde ich mir herunterladen.

Der schöne Günther 21. Sep 2021 16:48

AW: Automatische Referenzzählug bei Interfaces
 
Zitat:

Zitat von nb78 (Beitrag 1495204)
Ich bin davon ausgegangen das Delphi dann den Speicher beim Verlassen des Blocks oder der Schleife automatisch freigibt. Das würde ja die "try finally free" Konstrukte sparen.

Die Erklärung ist man dir etwas schuldig geblieben:

Du hast völlig Recht wenn du meinst dass eine Variable vom Typ "Interface" automatisch freigegeben wird wenn ihr Referenzzähler auf Null geht. Bei einer lokalen Variable ist das üblicherweise der Fall wenn der Gültigkeitsbereich (for-Schleife, begin/end-Block, ...) zu Ende ist.

Nicht der Fall ist das natürlich wenn die Referenz noch irgendwo anders hin weitergegeben und dort gespeichert wird.


Bei den Inline-Variablen ist hier ein Sonderfall, der eigentlich keiner ist:

Angenommen du hast eine Klasse
Delphi-Quellcode:
TTerm = class(TInterfacedObject, ITerm)
.
Wenn du eine lokale Variable anlegst dann kann die bspw. vom Typ TTerm oder ITerm sein. Wenn du schon ein Interface hast, dann solltest du das auch benutzen und sparst dir, wenn du eine neue Instanz anlegst, auch gleich das
Delphi-Quellcode:
try..finally
mit. So wie man es schon immer auch von Strings oder dynamischen Arrays kennt...

Jetzt der Stolperstein:
Wenn du bspw. sagst
Delphi-Quellcode:
var meinTerm := TTerm.Create()
, dann fahr doch mal später mit der Maus über meinTerm und schau was der Computer meint von welchem Typ das ist. Erstaunt? Woher soll der Computer auch wissen dass für dich ein
Delphi-Quellcode:
ITerm
besser wäre? Du hast ja ganz explizit
Delphi-Quellcode:
TTerm.Create()
gesagt.

Und Variablen vom Typ TTerm werden natürlich nicht beim Verlassen des Gültigkeitsbereichs freigegeben, das war ja schon immer so. Wir müssen jetzt also nur einen Weg finden explizit zu sagen dass die Variable bitte vom Typ Interface ist.

Es gibt zwei Möglichkeiten mit denen du den Inline-Variablen nicht abschwören musst, die sind nämlich gar nicht böse.
  1. Du gibst dem Compiler explizit vor, von welchem Typ die Variable sein soll. Das hat man ja früher in den var-Blöcken vor dem begin auch immer getan:
    Delphi-Quellcode:
    var meinTerm: ITerm := TTerm.Create();
  2. Du verwendest einen Getter, Factory-Methode was auch immer, dessen Rückgabetyp direkt vom Typ
    Delphi-Quellcode:
    ITerm
    ist


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:25 Uhr.
Seite 1 von 2  1 2   

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