AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Unverständlicher Memory Leak: String-Initialisierung in Record

Unverständlicher Memory Leak: String-Initialisierung in Record

Ein Thema von s.h.a.r.k · begonnen am 25. Jun 2012 · letzter Beitrag vom 26. Jun 2012
Antwort Antwort
Benutzerbild von s.h.a.r.k
s.h.a.r.k

Registriert seit: 26. Mai 2004
3.159 Beiträge
 
#1

Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 15:58
Delphi-Version: XE2
Heyho,

tut mir echt Leid, wenn der Titel des Threads nicht so wirklich aussagekräftig ist, aber ich weiß nicht genau wie ich mein Problem sehr präzise beschreiben soll -- ich nehme gerne Verbesserungen entgegen Und zwar habe ich mir einen Record geschrieben, der auf zwei verschiedene Arten funktionieren soll. Erst mal aber der Code, um den es im folgenden geht:
Delphi-Quellcode:
unit ExArray;

interface

type
/// <summary></summary>
TExArray<T> = record
private const
  USE_FIELD_FARRAY = 'UseFArray';
private type
  TDynamicArray = array of T;
  TGenericArray = TArray<T>;
  PGenericArray = ^TGenericArray;
public type
  TToStringFunc = reference to function (Value: T): String;
private
  FData : TGenericArray;
  FArray : PGenericArray;

  // Dirty Workaround: I have to use a String here because Delphi does not initialize
  // a Pointer/Boolean/Integer value. So I have to "abuse" a String to have a flag.
  FUseFArray : String;

  function GetArrayPointer(): PGenericArray;
public
  class operator Implicit(Value: Pointer): TExArray<T>;

  function ToString(): String;
end;

implementation

function TExArray<T>.GetArrayPointer(): PGenericArray;
begin
  if (FUseFArray = USE_FIELD_FARRAY) then
    Result := FArray
  else
    Result := @FData;
end;

class operator TExArray<T>.Implicit(Value: Pointer): TExArray<T>;
begin
  Result.FArray := Value;
  Result.FUseFArray := USE_FIELD_FARRAY;
end;

function TExArray<T>.ToString(): String;
var
  PArray : PGenericArray;
begin
  PArray := GetArrayPointer();
  // Umwandeln der Array-Element in einen String...
  // Hier als Pseudocode:
  //
  // Result := '';
  // for Element in GetArrayPointer()^ do
  // Result := Result + ',' + Converter<T>.ToString(Element);
end;

end.
Es soll nun eben die folgenden beiden Möglichkeiten geben, den Code zu verwenden:
Delphi-Quellcode:
// 1. TExArray stellt eine Art Wrapper dar, der Array-Methoden zur Verfügung
// stellt und auf einem existierenden Array ausführt.
var
  A : TArray<string>;
begin
  SetLength(A, 10);
  // Initialisierung des Arrays A...

  Writeln(TExArray<string>(@A).ToString()); // Wichtig: das @ beachten!
end;

// 2. TExArray ist ein Record, der intern ein Array hält und Methoden bereit stellt,
// um dieses Array zu bearbeiten. Es muss somit kein explizites "externes" Array
// angelegt werden.
var
  A : TExArray<string>;
begin
  A.Init(['Foo', 'Bar', 'Test']);
  A.Sort();

  Writeln(A.ToString();)
end;
Ja, ich weiß, dass in der TExArray-Definition oben, keine Init und Sort Methoden angegeben sind, da dies nur eine gekürzte Fassung darstellt, die für das Problem relevant ist. Um nun diese beiden Versionen in einem Record zu beachten, habe ich einen Schalter eingeführt, die private Variable FUseFArray. Es ist ja leider so, dass ein Record keinerlei primitiven Variablen à la Boolean oder Integer initialisiert, einen String jedoch schon! Daher dieser "dirty Workaround"... Wird nun also Version 1 genutzt, so wird der Implicit-Opertator aufgerufen und FUseArray entsprechend gesetzt. Wird Version 2 genutzt ist FUseArray eben ein Leerstring, da dieser ja initialisiert wird, was bei einem String immer der Leerstring ist. Entsprechend wirkt sich das auf GetArrayPointer(), eine Getter-Methode für die Array-Daten -- es wird niemals direkt FData oder FArray zugegriffen, sondern immer über GetArrayPointer().

Ich weiß, ist ziemlich viel Vorgeplänkel bisher, aber nun komme ich zu meinem eigentlichen Problem: der obige Code von TExArray führt zu einem Memory Leak, unabhängig vom generisch Typ T. Und zwar hängt dies mit dem internen String zusammen, wobei ich sagen muss, dass ich keine Ahnung habe, warum dies der Fall ist. Interessanterweise hängt es aber auch noch davon ab, wie man das ganze aufruft:
Delphi-Quellcode:
var
  A : TArray<Integer>;
  EA : TExArray<Integer>;
  S : string;
begin
  // Array-Initialisierung
  SetLength(A, 2);
  A[0] := 2;//'Foo';
  A[1] := 1;//'BAr';

  // Dieser Code erzeugt kein Memory Leak
  EA := TExArray<Integer>(@A);
  Writeln(EA.ToString());

  // Der hier aber schon
  Writeln(TExArray<Integer>(@A).ToString());

  // Und dieser hier auch
  S := TExArray<Integer>(@A).ToString();
  Writeln(S);
end.
Ich habe das Projekt mal angehängt, sodass ihr es direkt testen könnt, wenn ihr wollt -- FastMM sollte halt vorhanden sein. Ebenso findet ihr die Ausgabe von FastMM bzgl. dem Leak. Wäre echt froh um jeden Vorschlag, den ihr dies bzgl. habt, da ich keinerlei Ahnung habe, wieso das es hier Probleme gibt...
Angehängte Dateien
Dateityp: zip MemoryLeakTest.zip (1.019 Bytes, 1x aufgerufen)
Dateityp: txt MemoryLeakTest_MemoryManager_EventLog.txt (4,8 KB, 5x aufgerufen)
»Remember, the future maintainer is the person you should be writing code for, not the compiler.« (Nick Hodges)
  Mit Zitat antworten Zitat
Namenloser

Registriert seit: 7. Jun 2006
Ort: Karlsruhe
3.724 Beiträge
 
FreePascal / Lazarus
 
#2

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 16:11
Ich kann den Code leider nicht testen, weil ich kein Delphi mit Generics zur Verfügung habe, aber ich tippe auf einen Compiler-Bug. Bei früheren Versionen haben verschachtelete Record-Operatoren oft gar nicht erst funktioniert sondern interne Compiler-Fehler ausgelöst, sodass man gezwungen war, immer erst alles einer Variable zuzuweisen, bevor man etwas als Funktionsargument übergibt.

Finde deine Lösung mit dem String aber sehr interessant, weil ich ein ähnliches Record-Konstrukt verwende, um in Delphi 2006 einen nativen Unicode-String zu simulieren (weil WideString zu langsam ist). Ich hatte das gleiche Problem mit den nicht initialisierten Feldern aber habe es „gelöst“ durch manuelles Aufrufen einer Init- bzw. Free-Funktion, auch wenn mir das eigentlich gar [edit]nicht [/edit] gefällt. Mal schauen, ob ich es durch die String-Variante einfacher lösen kann... hoffentlich ohne Memory-Leak.[edit]Nein, geht leider nicht, weil ich am Ende den reservierten Platz freigeben muss. Ich könnte zwar ein Interface benutzen, aber dann wäre der Performance-Vorteil wohl dahin...[/edit]

Edit:

Aber sag mal, eigentlich geht es dir ja darum, so eine Art record/class helper für ein Array zu implementieren, oder? Wie wäre es, wenn du stattdessen einfach eine Klasse deklarierst und dann auf diese castest – dann wird das gecastete Array der aufgerufenen Methode als Self übergeben. Solange die Methoden nicht virtuell sind, sollte das klappen.

Z.B. so:
Delphi-Quellcode:
TArrayHelper<T> = class
public
  procedure SetLength(Length: integer);
  function GetLength: integer;
  property Length: integer read GetLength write SetLength;
end;

procedure TArrayHelper<T>.SetLength(Length: integer);
var
  Arr: array<T>;
begin
  // Ich bin mir gerade nicht sicher, ob Delphi den Typecast so schluckt,
  // aber mit ein bisschen Getrickse bekommt man den schon hin.
  Pointer(Arr) := Pointer(Self);
  System.SetLength(Arr, Length);
end;

function TArrayHelper<T>.GetLength: integer;
var
  Arr: array<T>;
begin
  Pointer(Arr) := Pointer(Self);
  Result := Length(Arr);
end;
Damit könnte man dann z.B. sowas machen:
Delphi-Quellcode:
var Arr: array<T>;

TArrayHelper<T>(Arr).Length := 10;
TArrayHelper<T>(Arr).Length := TArrayHelper<T>(Arr).Length + 2;

Geändert von Namenloser (25. Jun 2012 um 16:45 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von s.h.a.r.k
s.h.a.r.k

Registriert seit: 26. Mai 2004
3.159 Beiträge
 
#3

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 16:13
Ich hatte das gleiche Problem mit den nicht initialisierten Feldern aber habe es „gelöst“ durch manuelles Aufrufen einer Init- bzw. Free-Funktion, auch wenn mir das eigentlich gar gefällt.
Und genau das will ich verhinden Sonst kann ich ja gleich auf eine entsprechende Klasse umschwenken bzw. mit ein entsprechendes Interface dazu bauen.
»Remember, the future maintainer is the person you should be writing code for, not the compiler.« (Nick Hodges)
  Mit Zitat antworten Zitat
Daniel
(Administrator)

Registriert seit: 30. Mai 2002
Ort: Hamburg
14.610 Beiträge
 
Delphi 10.3 Rio
 
#4

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 16:37
Das Ganze geht doch in Richtung "nullable Types"?
Kennst Du dazu den Blog-Eintrag von Allen Bauer:
http://blogs.embarcadero.com/abauer/2008/09/18/38869

Dies wäre ein Weg, ohne internen String auszukommen.
Daniel R. Wolf
Admin Delphi-PRAXiS
mit Grüßen aus Hamburg
  Mit Zitat antworten Zitat
Benutzerbild von s.h.a.r.k
s.h.a.r.k

Registriert seit: 26. Mai 2004
3.159 Beiträge
 
#5

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 16:45
Das Ganze geht doch in Richtung "nullable Types"?
Kennst Du dazu den Blog-Eintrag von Allen Bauer:
http://blogs.embarcadero.com/abauer/2008/09/18/38869

Dies wäre ein Weg, ohne internen String auszukommen.
Ich glaube, dass ich das noch nicht kenne... Werde es gleich mal anschauen.
»Remember, the future maintainer is the person you should be writing code for, not the compiler.« (Nick Hodges)
  Mit Zitat antworten Zitat
shmia

Registriert seit: 2. Mär 2004
5.508 Beiträge
 
Delphi 5 Professional
 
#6

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 17:02
Sonst kann ich ja gleich auf eine entsprechende Klasse umschwenken bzw. mit ein entsprechendes Interface dazu bauen.
Du versuchst Records und Generics >100% auszureizen.
Mal angenommen, jemand der vielleicht nur 2 Jahre in der Schule Pascal & Delphi gelernt hat müsste jetzt deinen Code pflegen.
Er liest dann "Dirty Workaround" und sieht jede Menge Zeiger und "wilde Casts".
Könnte mir vorstellen, dass man die WTFs kaum noch zählen kann.

Nix für ungut, aber mit normalen Klassen wäre der Code besser verständlich und offener für Erweiterungen.
Andreas
  Mit Zitat antworten Zitat
Benutzerbild von s.h.a.r.k
s.h.a.r.k

Registriert seit: 26. Mai 2004
3.159 Beiträge
 
#7

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 25. Jun 2012, 18:05
Sonst kann ich ja gleich auf eine entsprechende Klasse umschwenken bzw. mit ein entsprechendes Interface dazu bauen.
Du versuchst Records und Generics >100% auszureizen.
Mal angenommen, jemand der vielleicht nur 2 Jahre in der Schule Pascal & Delphi gelernt hat müsste jetzt deinen Code pflegen.
Er liest dann "Dirty Workaround" und sieht jede Menge Zeiger und "wilde Casts".
Könnte mir vorstellen, dass man die WTFs kaum noch zählen kann.

Nix für ungut, aber mit normalen Klassen wäre der Code besser verständlich und offener für Erweiterungen.
Sehr verständlich, da stimme ich dir schon zu! Aber hier geht es mir primär um die Nutzung der Methoden und den so entstehenden Code so gering wie möglich zu halten -- wenn ich die Möglichkeit habe, das gleiche Ergebnis mit nur einer Zeile Code zu erhalten, dann ziehe ich diese Möglichkeit meist(!) einer anderen vor. Natürlich behalte ich die Les- und Wartbarkeit im Auge! Aber wie viele Nutzer schauen wirklich diese Library an und ändern daran was?

In den meisten Fällen würde ich die voll und ganz zustimmen, dass man hier eine entsprechende Klasse vorziehen und das Array darin kapseln sollte, aber hin und wieder will ich das eben nicht haben, da es einfach zu viel Overhead wäre. Daher strebe ich eben diese Lösung an und sie soll eben auch funktionieren. Trotzdem vielen Dank für deinen Beitrag. Ich denke, dass der Hinweis für viele andere durchaus nützlich ist
»Remember, the future maintainer is the person you should be writing code for, not the compiler.« (Nick Hodges)
  Mit Zitat antworten Zitat
Furtbichler
(Gast)

n/a Beiträge
 
#8

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 26. Jun 2012, 08:11
Eherlich gesagt finde ich es unglücklich, wenn ein Datentyp auf so unterschiedliche Art und weise verwendet wird. Solche 'tollen' Datentypen sind auch für den Leser unverständlich.

Ich würde schon damit anfangen, das derjenige, der sowas toll findet und so programmiert, seinen Stil hinterfragen sollte. Die Motivation kann ich zwar nachvollziehen, aber beim Programmieren geht es imho nicht um das kurze und knappe, sondern um das lesbare und wartbare.

Aber Stilfragen sind Religionsfragen und so lassen wir jeden das Seine.

Aber als Brainfuckchallenge taugt es allemal und hat Niveau.
  Mit Zitat antworten Zitat
Benutzerbild von s.h.a.r.k
s.h.a.r.k

Registriert seit: 26. Mai 2004
3.159 Beiträge
 
#9

AW: Unverständlicher Memory Leak: String-Initialisierung in Record

  Alt 26. Jun 2012, 10:11
Das Ganze geht doch in Richtung "nullable Types"?
Kennst Du dazu den Blog-Eintrag von Allen Bauer:
http://blogs.embarcadero.com/abauer/2008/09/18/38869

Dies wäre ein Weg, ohne internen String auszukommen.
Danke dir! Es hat wunderbar funktioniert Auch wenn ich erst noch dahinterkommen muss, was genau da nun passiert
»Remember, the future maintainer is the person you should be writing code for, not the compiler.« (Nick Hodges)
  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 14:59 Uhr.
Powered by vBulletin® Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2019 by Daniel R. Wolf