Einzelnen Beitrag anzeigen

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, 16: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