Die Frage ist: Wie werden dynamische Arrays intern von Delphi verwaltet, wenn sie als Klassenmitglied festgelegt werden? Werden sie kopiert oder als Referenz übergeben? Delphi 10.3.3 verwendet.
Die UpdateArray
Methode löscht das erste Element aus dem Array. Die Array-Länge bleibt jedoch 2. Die UpdateArrayWithParam
Methode löscht auch das erste Element aus dem Array. Die Array-Länge wird jedoch korrekt auf 1 reduziert.
Hier ist ein Codebeispiel:
interface
type
TSomeRec = record
Name: string;
end;
TSomeRecArray = array of TSomeRec;
TSomeRecUpdate = class
Arr: TSomeRecArray;
procedure UpdateArray;
procedure UpdateArrayWithParam(var ParamArray: TSomeRecArray);
end;
implementation
procedure TSomeRecUpdate.UpdateArray;
begin
Delete(Arr, 0, 1);
end;
procedure TSomeRecUpdate.UpdateArrayWithParam(var ParamArray: TSomeRecArray);
begin
Delete(ParamArray, 0, 1);
end;
procedure Test;
var r: TSomeRec;
lArr: TSomeRecArray;
recUpdate: TSomeRecUpdate;
begin
lArr := [];
r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];
recUpdate := TSomeRecUpdate.Create;
recUpdate.Arr := lArr;
recUpdate.UpdateArray;
//(('def'), ('def')) <=== this is the result of copy watch value, WHY two values?
lArr := [];
r.Name := 'abc';
lArr := lArr + [r];
r.Name := 'def';
lArr := lArr + [r];
recUpdate.UpdateArrayWithParam(lArr);
//(('def')) <=== this is the result of copy watch value - WORKS
recUpdate.Free;
end;
Delete
Prozedur. Es muss das dynamische Array neu zuweisen, und daher müssen sich alle Zeiger darauf "bewegen". Aber es kennt nur einen dieser Hinweise, nämlich den, den Sie ihm geben.Antworten:
Das ist eine interessante Frage!
Da
Delete
ändert sich die Länge des dynamischen Arrays - genausoSetLength
tut - es hat die dynamische Array neu zu verteilen. Außerdem wird der ihm zugewiesene Zeiger auf diesen neuen Speicherort geändert. Aber offensichtlich kann es keine anderen Zeiger auf das alte dynamische Array ändern.Daher sollte die Referenzanzahl des alten dynamischen Arrays verringert und ein neues dynamisches Array mit einer Referenzanzahl von 1 erstellt werden. Der angegebene Zeiger
Delete
wird auf dieses neue dynamische Array gesetzt.Daher sollte das alte dynamische Array unberührt bleiben (mit Ausnahme der reduzierten Referenzanzahl natürlich). Dies ist im Wesentlichen für die ähnliche
SetLength
Funktion dokumentiert :Aber überraschenderweise passiert dies in diesem Fall nicht ganz.
Betrachten Sie dieses minimale Beispiel:
Ich habe die Werte so gewählt, dass sie leicht im Speicher zu erkennen sind (Alt + Strg + E).
Nach (1)
a
verweist$02A2C198
in meinem Testlauf auf:Hier beträgt der Referenzzähler 2 und die Arraylänge erwartungsgemäß 2. (Informationen zum internen Datenformat für dynamische Arrays finden Sie in der Dokumentation .)
Nach (2)
a = b
, das heißtPointer(a) = Pointer(b)
. Beide zeigen auf dasselbe dynamische Array, das jetzt so aussieht:Wie erwartet beträgt der Referenzzähler jetzt 3.
Nun wollen wir sehen, was nach (3) passiert.
a
zeigt jetzt auf ein neues dynamisches Array2A30F88
in meinem Testlauf:Wie erwartet hat dieses neue dynamische Array einen Referenzzähler von 1 und nur das "B-Element".
Ich würde erwarten, dass das alte dynamische Array, auf das
b
immer noch zeigt, wie zuvor aussieht, jedoch mit einer reduzierten Referenzanzahl von 2. Aber jetzt sieht es so aus:Obwohl der Referenzzähler tatsächlich auf 2 reduziert ist, wurde das erste Element geändert.
Mein Fazit ist das
(1) Es ist Bestandteil des
Delete
Verfahrensvertrags, dass alle anderen Verweise auf das ursprüngliche dynamische Array ungültig werden.oder
(2) Es sollte sich wie oben beschrieben verhalten. In diesem Fall handelt es sich um einen Fehler.
Leider wird dies in der Dokumentation des
Delete
Verfahrens überhaupt nicht erwähnt.Es fühlt sich an wie ein Käfer.
Update: Der RTL-Code
Ich habe mir den Quellcode der
Delete
Prozedur angesehen, und das ist ziemlich interessant.Es kann hilfreich sein, das Verhalten mit dem von zu vergleichen
SetLength
(da dieses korrekt funktioniert):Wenn der Referenzzähler des dynamischen Arrays 1 ist, wird
SetLength
einfach versucht, die Größe des Heap-Objekts zu ändern (und das Längenfeld des dynamischen Arrays zu aktualisieren).Andernfalls
SetLength
wird eine neue Heap-Zuordnung für ein neues dynamisches Array mit einem Referenzzähler von 1 vorgenommen. Der Referenzzähler des alten Arrays wird um 1 verringert.Auf diese Weise wird garantiert, dass der endgültige Referenzzähler immer ist
1
- entweder von Anfang an oder es wurde ein neues Array erstellt. (Es ist gut, dass Sie nicht immer eine neue Heap-Zuordnung vornehmen. Wenn Sie beispielsweise ein großes Array mit einer Referenzanzahl von 1 haben, ist es billiger, es einfach abzuschneiden, als es an einen neuen Speicherort zu kopieren.)Da
Delete
das Array immer kleiner wird, ist es verlockend, einfach zu versuchen, die Größe des Heap-Objekts dort zu reduzieren, wo es sich befindet. Und genau das versucht der RTL-CodeSystem._DynArrayDelete
. Daher wird in Ihrem Fall dasBBBBBBBB
an den Anfang des Arrays verschoben. Alles ist gut.Aber dann ruft es an
System.DynArraySetLength
, was auch von verwendet wirdSetLength
. Und dieses Verfahren enthält den folgenden Kommentar:bevor festgestellt wird, dass das Objekt tatsächlich gemeinsam genutzt wird (in unserem Fall ref count = 3), wird eine neue Heap-Zuordnung für ein neues dynamisches Array vorgenommen und das alte (reduzierte) an diesen neuen Speicherort kopiert. Es reduziert die Ref-Anzahl des alten Arrays und aktualisiert die Ref-Anzahl, Länge und den Argumentzeiger des neuen Arrays.
Also haben wir trotzdem ein neues dynamisches Array erhalten. Die RTL-Programmierer haben jedoch vergessen, dass sie das ursprüngliche Array, das jetzt aus dem neuen Array besteht, das über dem alten Array liegt, bereits durcheinander gebracht haben :
BBBBBBBB BBBBBBBB
.quelle
Delete
in einem dynamischen Array vermeiden . Zum einen ist es auf großen Arrays nicht billig (da es notwendigerweise viele Daten kopieren muss). Und diese aktuelle Ausgabe macht mir natürlich noch mehr Sorgen. Aber lassen Sie uns auch abwarten, ob die anderen Delphi-Mitglieder der SO-Community meiner Analyse zustimmen.SetLength
,Insert
undDelete
offensichtlich neu zu verteilen müssen. Das Ändern eines Elements (wieb[2] := 4
) wirkt sich auf alle anderen dynamischen Array-Variablen aus, die auf dasselbe dynamische Array verweisen. Es wird keine Kopie geben.