La pregunta es: ¿cómo gestionan Array dinámicamente las matrices dinámicas cuando se configuran como miembros de la clase? ¿Se copian o se pasan por referencia? Delphi 10.3.3 utilizado.
El UpdateArray
método elimina el primer elemento de la matriz. Pero la longitud de la matriz permanece 2. El UpdateArrayWithParam
método también elimina el primer elemento de la matriz. Pero la longitud de la matriz se reduce correctamente a 1.
Aquí hay un ejemplo de código:
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
procedimiento. Tiene que reasignar la matriz dinámica, por lo que todos los punteros a ella "deben" moverse. Pero solo conoce uno de estos indicadores, a saber, el que le das.Respuestas:
¡Esta es una pregunta interesante!
Dado que
Delete
cambia la longitud de la matriz dinámica , al igual que loSetLength
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 :Pero sorprendentemente, esto no sucede en este caso.
Considere este ejemplo mínimo:
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: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í: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ámica2A30F88
en mi ejecución de prueba: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í: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 RTLSystem._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 porSetLength
. Y este procedimiento contiene el siguiente comentario,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
.fuente
Delete
en una matriz dinámica. Por un lado, no es barato en matrices grandes (ya que necesariamente necesita copiar una gran cantidad de datos). Y este problema actual me preocupa aún más, obviamente. Pero también esperemos y veamos si los otros miembros de Delphi de la comunidad SO están de acuerdo con mi análisis.SetLength
,Insert
yDelete
obviamente necesidad de reasignar. Simplemente cambiar un elemento (comob[2] := 4
) afectará a cualquier otra variable de matriz dinámica que apunte a la misma matriz dinámica; No habrá copia.