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 :
Im Anschluss an einen Anruf SetLength
, S
garantiert eine eindeutige Zeichenfolge oder ein Array verweisen - , die einen String oder ein Array mit einem Referenzzähler ist.
Aber überraschenderweise passiert dies in diesem Fall nicht ganz.
Betrachten Sie dieses minimale Beispiel:
procedure TForm1.FormCreate(Sender: TObject);
var
a, b: array of Integer;
begin
a := [$AAAAAAAA, $BBBBBBBB]; {1}
b := a; {2}
Delete(a, 0, 1); {3}
end;
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:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
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ßt Pointer(a) = Pointer(b)
. Beide zeigen auf dasselbe dynamische Array, das jetzt so aussieht:
02A2C190 03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Wie erwartet beträgt der Referenzzähler jetzt 3.
Nun wollen wir sehen, was nach (3) passiert. a
zeigt jetzt auf ein neues dynamisches Array 2A30F88
in meinem Testlauf:
02A30F80 01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00
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:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB
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-Code System._DynArrayDelete
. Daher wird in Ihrem Fall das BBBBBBBB
an den Anfang des Arrays verschoben. Alles ist gut.
Aber dann ruft es an System.DynArraySetLength
, was auch von verwendet wird SetLength
. Und dieses Verfahren enthält den folgenden Kommentar:
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
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
.