AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Automatische Referenzzählug bei Interfaces

Automatische Referenzzählug bei Interfaces

Offene Frage von "himitsu"
Ein Thema von nb78 · begonnen am 21. Sep 2021 · letzter Beitrag vom 23. Sep 2021
Antwort Antwort
Seite 1 von 2  1 2   
nb78

Registriert seit: 23. Dez 2009
6 Beiträge
 
Delphi 2010 Professional
 
#1

Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 09:38
Delphi-Version: 10.4 Sydney
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
Angehängte Dateien
Dateityp: zip memoryLeakDebugger.zip (33,2 KB, 4x aufgerufen)
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.336 Beiträge
 
Delphi 11 Alexandria
 
#2

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 10:14
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 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.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)

Geändert von stahli (21. Sep 2021 um 10:16 Uhr)
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.108 Beiträge
 
Delphi 10 Seattle Enterprise
 
#3

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 10:17
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.
Angehängte Dateien
Dateityp: txt memoryLeakDebugger_MemoryManager_EventLog.txt (61,3 KB, 8x aufgerufen)
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.108 Beiträge
 
Delphi 10 Seattle Enterprise
 
#4

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 10:21
Ohne mir den Quellcode jetzt genauer angesehen zu haben wären mein spontaner Verdacht zirkuläre Referenzen: Angenommen ein ITerm a verweist auf einen Term b und der verweist auch wieder auf a .

Angenommen in deinem Code interessiert sich mittlerweile niemand mehr für a oder 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.
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.059 Beiträge
 
Delphi 10.4 Sydney
 
#5

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 11:30
Überall wo du mit inline variables arbeitest und sowas schreibst:
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 var newOp1:=TBlablaTerm.create aufrufst, wird eine Instanz von TBlablaTerm erstellt und keine "Interface-Instanz" vom Typ ITerm.

Geändert von TiGü (21. Sep 2021 um 11:36 Uhr)
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.059 Beiträge
 
Delphi 10.4 Sydney
 
#6

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 11:34
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;
...
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.323 Beiträge
 
Delphi 11 Alexandria
 
#7

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 11:50
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...
Sebastian Jänicke
Alle eigenen Projekte sind eingestellt, ebenso meine Homepage, Downloadlinks usw. im Forum bleiben aktiv!
  Mit Zitat antworten Zitat
nb78

Registriert seit: 23. Dez 2009
6 Beiträge
 
Delphi 2010 Professional
 
#8

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 15:13
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.
  Mit Zitat antworten Zitat
nb78

Registriert seit: 23. Dez 2009
6 Beiträge
 
Delphi 2010 Professional
 
#9

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 15:16
@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.
  Mit Zitat antworten Zitat
Der schöne Günther

Registriert seit: 6. Mär 2013
6.108 Beiträge
 
Delphi 10 Seattle Enterprise
 
#10

AW: Automatische Referenzzählug bei Interfaces

  Alt 21. Sep 2021, 15:48
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 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 try..finally mit. So wie man es schon immer auch von Strings oder dynamischen Arrays kennt...

Jetzt der Stolperstein:
Wenn du bspw. sagst 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 ITerm besser wäre? Du hast ja ganz explizit 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: var meinTerm: ITerm := TTerm.Create();
  2. Du verwendest einen Getter, Factory-Methode was auch immer, dessen Rückgabetyp direkt vom Typ ITerm ist
  Mit Zitat antworten Zitat
Themen-Optionen Thema durchsuchen
Thema durchsuchen:

Erweiterte Suche
Ansicht

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 08:20 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz