AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Programmieren allgemein Delphi Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Array
Thema durchsuchen
Ansicht
Themen-Optionen

Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Array

Ein Thema von TiGü · begonnen am 12. Aug 2019 · letzter Beitrag vom 12. Aug 2019
Antwort Antwort
TiGü

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

Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Array

  Alt 12. Aug 2019, 14:54
Hallo Gemeinde,

Vorgeschichte:
Ich bin gerade dabei im jahre alten Quelltexten Sachen zu optimieren. Delphi-Version ist XE5.
Ein Kernstück des vorhandenen Frameworks basiert darauf, sich komplexe Result-Werte als records quer durchs Programm und auch über Modulgrenzen hinweg zu schicken.

Optimierungsbedarf:
Für String-Informationen wurden dafür fixe/statische Char-Arrays verwendet, stellenweise 1000 bis 2000 Zeichen groß, obwohl in 99 % der Fälle viel weniger reichen würden.

Lösungsidee:
Meine Idee ist, diese statischen Char-Arrays mit dem System.WideString (BSTR) zu ersetzen.
So kann das auch über Modulgrenzen hinweg genutzt werden, die Speicherverwaltung der Strings macht Windows und man hat viel weniger eigentlichen Speicherbedarf, da die Strings halt nur so lang sind, wie man reinsteckt.

Problem:
Es gibt ein generisches Container-Record, in das per eigenen Implicit-Operator die - ich sag mal Sub-Records - transformiert werden können.
Dieses Container-Record speichert die Sub-Records als Byte-Array.
Dabei hat mir FastMM ein Problem aufgezeigt.
Ich habe das Problem anhand eines minimalen Beispiels mit Kommentaren skizziert.
Anschließend befindet sich die Meldung vom externen FastMM (ja, den mit extra DLL).
Ich weiß, dass mein Problem die Sache mit den Record im Record ist, weil so die Referenzzählung vom WideString-Array beim Move kaputt geht.
Aber ich bin gerade so vernagelt und finde keine Lösung.
Hat jemand eine Idee, wie ich die Idee in diesem Rahmen lösen kann?

Delphi-Quellcode:
program MinimalExample;

{$APPTYPE CONSOLE}

{$R *.res}


uses
    FastMM4,
    System.SysUtils;

type
    TContainer = record
        ExternalData: TBytes;
    end;

    TElementA = record
        MyStrings: TArray<Widestring>;
        MyNumber: UInt64;
    end;

    TElementB = record
    public
      type
        TElementBData = record
            A: TElementA;
        end;
    public
        Data: TElementBData;
    end;

procedure Main;
var
    A: TElementA;
    B, B2: TElementB;
    Container: TContainer;
    I: Integer;
    CopyLength: Integer;
begin
    // TElementA zum Leben erwecken und füllen
    A := System.Default(TElementA);
    SetLength(A.MyStrings, 5);
    for I := System.Low(A.MyStrings) to System.High(A.MyStrings) do
    begin
        A.MyStrings[I] := Format('%d%d%d', [I, I, I]);
    end;

    // TElementA dem TElementB.Data unterschieben. ASM: call @CopyRecord.
    // Im echten Quelltext ein Implicit-Operator an TElementB dran.
    B := System.Default(TElementB);
    B.Data.A := A;

    // Container-Record, was die Daten von TElementB in einen Byte-Array halten soll.
    // Im echten Quelltext eine externe Funktion an TContainer, der ich sozusagen nur Pointer und Größe auf B.Data gebe.
    Container := System.Default(TContainer);
    CopyLength := SizeOf(B.Data);
    SetLength(Container.ExternalData, CopyLength);
    // ---> Das Move zerstört wahrscheinlich den RefCount vom B.Data.A.Strings-Array
    Move((@B.Data)^, Container.ExternalData[0], CopyLength);

    // Zurückumwandeln von Container-Element
    // Im echten Quelltext auch wieder ein Implicit-Operator an TElementB dran.
    B2 := System.Default(TElementB);
    // ---> Das Move kopiert zwar den Pointer vom Array, so das B2.Data.A.Strings gefüllt ist
    // ---> aber hinterher FastMM zurecht sagt, dass man dafür in die Delphi-Hölle kommt.
    CopyLength := Length(Container.ExternalData);
    Move(Container.ExternalData[0], B2.Data, CopyLength);

    for I := System.Low(B2.Data.A.MyStrings) to System.High(B2.Data.A.MyStrings) do
    begin
        Writeln(B2.Data.A.MyStrings[I]);
    end;
end;

begin
    Main;
end.
Code:
---------------------------
MinimalExample.exe: Memory Error Detected
---------------------------
FastMM has detected an error during a free block scan operation. FastMM detected that a block has been modified after being freed.

Modified byte offsets (and lengths): 0(1)

The previous block size was: 28

This block was previously allocated by thread 0x36E0, and the stack trace (return addresses) at the time was:
4041F9 [System.pas][System][@ReallocMem$qqrrpvi][4508]
407A0D [System.pas][System][DynArraySetLength$qqrrpvpvipi][33677]
407B3E [System.pas][System][@DynArraySetLength$qqrv][33756]
41DEED [MinimalExample.dpr][MinimalExample][Main$qqrv][42]
406498 [System.pas][System][InitUnits$qqrv][21918]
406504 [System.pas][System][@StartExe$qqrp23System.PackageInfoTablep17System.TLibModule][22052]
4203F5 
771A6359 [BaseThreadInitThunk]
77B67A94 [RtlGetAppContainerNamedObjectPath]
77B67A64 [RtlGetAppContainerNamedObjectPath]


The allocation number was: 113


The block was previously freed by thread 0x36E0, and the stack trace (return addresses) at the time was:
407B7A [System.pas][System][@DynArrayClear$qqrrpvpv][33939]
4075DE [System.pas][System][@FinalizeArray$qqrpvt1ui][31219]
4074BD [System.pas][System][@FinalizeRecord$qqrpvt1][30891]
4075BD [System.pas][System][@FinalizeArray$qqrpvt1ui][31178]
4074BD [System.pas][System][@FinalizeRecord$qqrpvt1][30891]
4075BD [System.pas][System][@FinalizeArray$qqrpvt1ui][31178]
4074BD [System.pas][System][@FinalizeRecord$qqrpvt1][30891]
4075BD [System.pas][System][@FinalizeArray$qqrpvt1ui][31178]
41E04F [MinimalExample][Main$qqrv]
4203F5 
771A6359 [BaseThreadInitThunk]


The current thread ID is 0x36E0, and the stack trace (return addresses) leading to this error is:
40D780 [FastMM4.pas][FastMM4][CheckBlocksOnShutdown$qqro][11424]
40E7C4 [FastMM4.pas][FastMM4][FinalizeMemoryManager$qqrv][12966]
40E830 [FastMM4.pas][FastMM4][Finalization$qqrv][13065]
40642C [System.pas][System][FinalizeUnits$qqrv][21786]
406826 [System.pas][System][@Halt0$qqrv][23185]
4203FA
771A6359 [BaseThreadInitThunk]
77B67A94 [RtlGetAppContainerNamedObjectPath]
77B67A64 [RtlGetAppContainerNamedObjectPath]



Current memory dump of 256 bytes starting at pointer address 7F93B690:
87 92 42 00 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 2C A7 7C 9E
80 80 80 80 80 80 80 80 00 00 00 00 11 B6 93 7F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
7C 00 00 00 F9 41 40 00 0D 7A 40 00 3E 7B 40 00 AB DF 41 00 F5 03 42 00 59 63 1A 77 94 7A B6 77
64 7A B6 77 00 00 00 00 00 00 00 00 00 00 00 00 E0 36 00 00 E0 36 00 00 A6 41 40 00 7A 7B 40 00
DE 75 40 00 BD 74 40 00 6B E0 41 00 F5 03 42 00 59 63 1A 77 94 7A B6 77 64 7A B6 77 00 00 00 00
00 00 00 00 18 00 00 00 00 00 00 00 B5 7C 6D 4F 88 92 42 00 80 80 80 80 80 80 80 80 80 80 80 80
80 80 80 80 80 80 80 80 4A 83 92 B0 80 80 80 80 80 80 80 80 80 80 80 80 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
‡  ’  B . €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  €  , §  |  ž
€  €  €  €  €  €  €  €  . . . . . ¶  “    . . . . . . . . . . . . . . . .
|  . . . ù  A @  . . z @  . > {  @  . «  ß  A . õ  . B . Y c . w ”  z ¶  w
d z ¶  w . . . . . . . . . . . . à  6  . . à  6  . . ¦  A @  . z {  @  .
Þ  u @  . ½  t @  . k à  A . õ  . B . Y c . w ”  z ¶  w d z ¶  w . . . .
. . . . . . . . . . . . µ  |  m O ˆ  ’  B . €  €  €  €  €  €  €  €  €  €  €  €
€  €  €  €  €  €  €  €  J ƒ  ’  °  €  €  €  €  €  €  €  €  €  €  €  €  . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

---------------------------
OK  
---------------------------
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.009 Beiträge
 
Delphi 12 Athens
 
#2

AW: Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Arra

  Alt 12. Aug 2019, 15:36
Dir ist aber schon bewusst, daß ein TArray<Widestring> nur ein Pointer auf mehrere Pointer zu je einem array of WideChar ist? Das Move kopiert aber nur den Pointer des TArray<WideString> und nicht die dahinter liegenden Inhalte. Ich glaube kaum, daß dieses Verhalten das gewünschte ist.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
peterbelow

Registriert seit: 12. Jan 2019
Ort: Hessen
672 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Arra

  Alt 12. Aug 2019, 15:48
Hallo Gemeinde,

Vorgeschichte:
Ich bin gerade dabei im jahre alten Quelltexten Sachen zu optimieren. Delphi-Version ist XE5.
Ein Kernstück des vorhandenen Frameworks basiert darauf, sich komplexe Result-Werte als records quer durchs Programm und auch über Modulgrenzen hinweg zu schicken.

Optimierungsbedarf:
Für String-Informationen wurden dafür fixe/statische Char-Arrays verwendet, stellenweise 1000 bis 2000 Zeichen groß, obwohl in 99 % der Fälle viel weniger reichen würden.

Lösungsidee:
Meine Idee ist, diese statischen Char-Arrays mit dem System.WideString (BSTR) zu ersetzen.
So kann das auch über Modulgrenzen hinweg genutzt werden, die Speicherverwaltung der Strings macht Windows und man hat viel weniger eigentlichen Speicherbedarf, da die Strings halt nur so lang sind, wie man reinsteckt.

Problem:
Es gibt ein generisches Container-Record, in das per eigenen Implicit-Operator die - ich sag mal Sub-Records - transformiert werden können.
Dieses Container-Record speichert die Sub-Records als Byte-Array.
Dabei hat mir FastMM ein Problem aufgezeigt.
Ich habe das Problem anhand eines minimalen Beispiels mit Kommentaren skizziert.
Anschließend befindet sich die Meldung vom externen FastMM (ja, den mit extra DLL).
Ich weiß, dass mein Problem die Sache mit den Record im Record ist, weil so die Referenzzählung vom WideString-Array beim Move kaputt geht.
Aber ich bin gerade so vernagelt und finde keine Lösung.
Hat jemand eine Idee, wie ich die Idee in diesem Rahmen lösen kann?
Du hast da einen Denkfehler drin. Deine alten array [0..x] of char felder sind value-typen, können also ohne Probleme von speicher zu speicher kopiert werden. Ein TArray<Widestring> ist aber ein referenz-Typ, genau wie Widestring selbst.


Delphi-Quellcode:
type
    TContainer = record
        ExternalData: TBytes;
    end;

    TElementA = record
        MyStrings: TArray<Widestring>;
        MyNumber: UInt64;
    end;

    TElementB = record
    public
      type
        TElementBData = record
            A: TElementA;
        end;
    public
        Data: TElementBData;
    end;
   --snip--
    B.Data.A := A;

    // Container-Record, was die Daten von TElementB in einen Byte-Array halten soll.
    // Im echten Quelltext eine externe Funktion an TContainer, der ich sozusagen nur Pointer und Größe auf B.Data gebe.
    Container := System.Default(TContainer);
    CopyLength := SizeOf(B.Data);
Und da knalls schon, sizeof(B.Data) == sizeof(TElementA) == sizeof(pointer)+sizeof(uint64).

D. h. die Widestrings in dem MYStrings array sind selbst garnicht mitgezählt. Was Du dann kopierst ist die Addresse des Arrays, nicht sein Inhalt. Und den kannst Du auch garnicht blind kopierene, da der Array ja auch nur Referenzen enthält, nicht direkt die zu kopierenden Zeichen.

Du brauchst für jeden Record-Typ Methoden, die den Inhalt in einen Byte-Array kopieren (streamen) und daraus wieder restaurieren können. Das geht nicht wirklich generisch, obwohl man da unter Verwendung von RTTI ziemlich weit kommen kann. Ich weis nicht, wie gut das Marshalling für JSON Support bei XE5 schon war, aber das macht was ähnliches.
Peter Below
  Mit Zitat antworten Zitat
TiGü

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

AW: Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Arra

  Alt 12. Aug 2019, 15:52
Ja eben, im Prinzip müsste man die Zeile mit Move mit irgendetwas kluges ersetzen, ohne das es allzu langsamer ist.

Vorher, mit den festen String-Arrays, war das mit dem Move natürlich nicht so das Problem, dafür waren die resultierenden Records teilweise über 4000 Byte groß, obwohl davon nur ganz wenig gebraucht wurde.

Ich arbeite behelfsmäßig mit einen fixen Array (array[0..19] of Widestring), das knallt dann zumindest nicht sofort.
Das ist aber nur ein Workaround und nicht die endgültige Lösung...
  Mit Zitat antworten Zitat
peterbelow

Registriert seit: 12. Jan 2019
Ort: Hessen
672 Beiträge
 
Delphi 11 Alexandria
 
#5

AW: Memory Error Detected: TArray<System.WideString> im Record im Record im Byte-Arra

  Alt 12. Aug 2019, 16:35
Ja eben, im Prinzip müsste man die Zeile mit Move mit irgendetwas kluges ersetzen, ohne das es allzu langsamer ist.

Vorher, mit den festen String-Arrays, war das mit dem Move natürlich nicht so das Problem, dafür waren die resultierenden Records teilweise über 4000 Byte groß, obwohl davon nur ganz wenig gebraucht wurde.

Ich arbeite behelfsmäßig mit einen fixen Array (array[0..19] of Widestring), das knallt dann zumindest nicht sofort.
Das ist aber nur ein Workaround und nicht die endgültige Lösung...
Auch den kannst Du nicht einfach per Move kopieren!
Peter Below
  Mit Zitat antworten Zitat
Antwort Antwort


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 00:44 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