¡Esta es una pregunta interesante!
Dado que Delete
cambia la longitud de la matriz dinámica , al igual que lo SetLength
hace, tiene que reasignar la matriz dinámica. Y también cambia el puntero que se le asigna a esta nueva ubicación en la memoria. Pero obviamente no puede cambiar ningún otro puntero a la matriz dinámica anterior.
Por lo tanto, debería disminuir el recuento de referencia de la matriz dinámica anterior y crear una nueva matriz dinámica con un recuento de referencia de 1. El puntero dado a Delete
se establecerá en esta nueva matriz dinámica.
Por lo tanto, la matriz dinámica anterior debe estar intacta (a excepción de su recuento de referencia reducido, por supuesto). Esto está esencialmente documentado para la SetLength
función similar :
Después de una llamada a SetLength
, S
se garantiza que hace referencia a una cadena o matriz única, es decir, una cadena o matriz con un recuento de referencia de uno.
Pero sorprendentemente, esto no sucede en este caso.
Considere este ejemplo mínimo:
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;
Elegí los valores para que sean fáciles de detectar en la memoria (Alt + Ctrl + E).
Después de (1), a
señala $02A2C198
en mi ejecución de prueba:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Aquí el recuento de referencia es 2 y la longitud de la matriz es 2, como se esperaba. (Consulte la documentación del formato de datos interno para matrices dinámicas).
Después de (2), a = b
, es decir, Pointer(a) = Pointer(b)
. Ambos apuntan a la misma matriz dinámica, que ahora se ve así:
02A2C190 03 00 00 00 02 00 00 00
02A2C198 AA AA AA AA BB BB BB BB
Como se esperaba, el recuento de referencia ahora es 3.
Ahora, veamos qué sucede después de (3). a
ahora apunta a una nueva matriz dinámica 2A30F88
en mi ejecución de prueba:
02A30F80 01 00 00 00 01 00 00 00
02A30F88 BB BB BB BB 01 00 00 00
Como se esperaba, esta nueva matriz dinámica tiene un recuento de referencia de 1 y solo el "elemento B".
Esperaría que la matriz dinámica anterior, que b
todavía apunta, se vea como antes pero con un recuento de referencia reducido de 2. Pero ahora se ve así:
02A2C190 02 00 00 00 02 00 00 00
02A2C198 BB BB BB BB BB BB BB BB
Aunque el recuento de referencia se reduce a 2, el primer elemento ha cambiado.
Mi conclusión es que
(1) Es parte del contrato del Delete
procedimiento que invalida todas las demás referencias a la matriz dinámica inicial.
o
(2) Debería comportarse como describí anteriormente, en cuyo caso esto es un error.
Desafortunadamente, la documentación del Delete
procedimiento no menciona esto en absoluto.
Se siente como un error.
Actualización: el código RTL
Eché un vistazo al código fuente del Delete
procedimiento, y esto es bastante interesante.
Puede ser útil comparar el comportamiento con el de SetLength
(porque ese funciona correctamente):
Si el recuento de referencia de la matriz dinámica es 1, SetLength
intenta simplemente cambiar el tamaño del objeto de montón (y actualizar el campo de longitud de la matriz dinámica).
De lo contrario, SetLength
realiza una nueva asignación de montón para una nueva matriz dinámica con un recuento de referencia de 1. El recuento de referencia de la matriz anterior se reduce en 1.
De esta forma, se garantiza que el recuento de referencia final sea siempre 1
, ya sea desde el principio o se ha creado una nueva matriz. (Es bueno que no siempre haga una nueva asignación de almacenamiento dinámico. Por ejemplo, si tiene una matriz grande con un recuento de referencia de 1, simplemente truncarla es más barato que copiarla en una nueva ubicación).
Ahora, como Delete
siempre hace que la matriz sea más pequeña, es tentador intentar simplemente reducir el tamaño del objeto de montón donde está. Y esto es de hecho lo que intenta el código RTL System._DynArrayDelete
. Por lo tanto, en su caso, BBBBBBBB
se mueve al principio de la matriz. Todo está bien.
Pero luego llama System.DynArraySetLength
, que también es utilizado por SetLength
. Y este procedimiento contiene el siguiente comentario,
// If the heap object isn't shared (ref count = 1), just resize it. Otherwise, we make a copy
antes de detectar que el objeto está compartido (en nuestro caso, rec count = 3), realiza una nueva asignación de montón para una nueva matriz dinámica y copia la antigua (reducida) en esta nueva ubicación. Reduce el recuento de referencias de la matriz anterior y actualiza el recuento de referencias, la longitud y el puntero de argumento de la nueva.
Así que terminamos con una nueva matriz dinámica de todos modos. Pero los programadores RTL olvidaron que ya habían ensuciado la matriz original, que ahora consiste en la nueva matriz colocada encima de la anterior: BBBBBBBB BBBBBBBB
.