length dinamico crear bidimensional arreglo arrayofstring array delphi dynamic-arrays

delphi - dinamico - ¿Por qué no pueden dos TBytes usar datos superpuestos?



length of array delphi (4)

Déjame guiarte a través de las formas en que falla este código y cómo el compilador te permite dispararte en el pie.

Si recorres el código utilizando el depurador, puedes ver qué sucede.

Después de la inicialización de Thing1 , puede ver que FData se llena con todos los ceros.
Curiosamente, Thing2 está bien.
Por lo tanto, el error está en CreateThing . Vamos a investigar más a fondo ...

En el constructor de nombre extraño CreateThing tiene la siguiente línea:

FData := @FBuf[1];

Esto parece una tarea simple, pero es realmente una llamada a DynArrayAssign

Project97.dpr.32: FData := @FBuf[1]; 0042373A 8B45FC mov eax,[ebp-$04] 0042373D 83C008 add eax,$08 00423743 8B5204 mov edx,[edx+$04] 00423746 42 inc edx 00423747 8B0DE03C4000 mov ecx,[$00403ce0] 0042374D E8E66DFEFF call @DynArrayAsg <<-- lots of stuff happening here.

Una de las comprobaciones que realiza DynArrayAsg es verificar si la matriz dinámica de origen está vacía o no.
DynArrayAsg también hace algunas otras cosas que debe tener en cuenta.

Primero veamos la estructura de una matriz dinámica ; ¡No es solo un simple puntero a una matriz!

Offset 32/64 | Contents --------------+-------------------------------------------------------------- -8/-12 | 32 bit reference count -4/-8 | 32 or 64 bit length indicator 0/ 0 | data of the array.

FData = @FBuf[1] se está metiendo con los campos de prefijo de la matriz dinámica.
Los 4 bytes delante de @Fbuf[1] se interpretan como la longitud.
Para Thing1 estos son:

-8 (refcnt) -4 (len) 0 (data) FBuf: 01 00 00 00 08 00 00 00 00 ''S'' ''n'' ..... FData: 00 00 00 08 00 00 00 00 .............. //Hey that''s a zero length.

Vaya, cuando DynArrayAsg comienza a investigar, ve que lo que cree que es la fuente de la asignación tiene una longitud de cero, es decir, piensa que la fuente está vacía y no asigna nada. Deja FData sin cambios!

¿ Thing2 funciona según lo previsto?
Parece que sí, pero en realidad falla de una manera bastante mala, déjame mostrarte.

Has engañado con éxito el tiempo de ejecución para que @FBuf[1] es una referencia válida a una matriz dinámica.
Debido a esto, el puntero FData se ha actualizado para apuntar a FBuf[1] (hasta el momento bueno), y el recuento de referencia de FData se ha incrementado en 1 (no es bueno), también el tiempo de ejecución ha aumentado el bloque de memoria que contiene la dinámica arreglo a lo que cree que es el tamaño correcto para FData (mal).

-8 (refcnt) -4 (len) 0 (data) FBuf: 01 01 00 00 13 00 00 00 01 ''S'' ''n'' ..... FData: 01 00 00 13 00 00 00 01 ''S'' ..............

Oops FData ahora tiene un número de referencia de 318,767,105 y una longitud de 16,777,216 bytes.
FBuf también ha aumentado su longitud, pero su referencia ahora es de 257.

Es por eso que necesita la llamada a SetLength para deshacer la SetLength masiva de la memoria. Esto todavía no soluciona los recuentos de referencia sin embargo.
La ubicación general puede causar errores de falta de memoria (especialmente en 64 bits) y los recuentos extraños causan una pérdida de memoria porque sus arreglos nunca se liberarán.

La solución
De acuerdo con la respuesta de David: habilitar los punteros {$TYPEDADDRESS ON} : {$TYPEDADDRESS ON}

Puede corregir el código definiendo FData como un PAnsiChar o PByte normal.
Si se asegura de terminar siempre sus asignaciones a FBuf con un FData de doble cero, funcionará como se esperaba.

Haz de FData un TBuffer así:

TBuffer = record private FData : PByte; function GetLength: cardinal; function GetType: byte; public class operator implicit(const A: TBytes): TBuffer; class operator implicit(const A: TBuffer): PByte; property Length: cardinal read GetLength; property DataType: byte read GetType; end;

Reescribe CreateThing manera:

constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes); begin SetLength(FBuf, Length(AThingData) + Sizeof(AThingType) + 2); FBuf[0] := AThingType; Move(AThingData[0], FBuf[1], Length(AThingData)); FBuf[Lengh(FBuf)-1]:= 0; FBuf[Lengh(FBuf)-2]:= 0; //trailing zeros for compatibility with pansichar FData := FBuf; //will call the implicit class operator. end; class operator TBuffer.implicit(const A: TBytes): TBuffer; begin Result.FData:= PByte(@A[1]); end;

No entiendo toda esta mierda acerca de tratar de ser más astuto que el compilador.
¿Por qué no declarar FData así?

type TMyData = record DataType: byte; Buffer: Ansistring; ....

Y trabajar con eso.

Considere el siguiente código XE6. La intención es que ThingData se escriba en la consola para Thing1 y Thing2 , pero no lo está. ¿Porqué es eso?

program BytesFiddle; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils; type TThing = class private FBuf : TBytes; FData : TBytes; function GetThingData: TBytes; function GetThingType: Byte; public property ThingType : Byte read GetThingType; property ThingData : TBytes read GetThingData; constructor CreateThing(const AThingType : Byte; const AThingData: TBytes); end; { TThing1 } constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes); begin SetLength(FBuf, Length(AThingData) + 1); FBuf[0] := AThingType; Move(AThingData[0], FBuf[1], Length(AThingData)); FData := @FBuf[1]; SetLength(FData, Length(FBuf) - 1); end; function TThing.GetThingData: TBytes; begin Result := FData; end; function TThing.GetThingType: Byte; begin Result := FBuf[0]; end; var Thing1, Thing2 : TThing; begin try Thing1 := TThing.CreateThing(0, TEncoding.UTF8.GetBytes(''Sneetch'')); Thing2 := TThing.CreateThing(1, TEncoding.UTF8.GetBytes(''Star Belly Sneetch'')); Writeln(TEncoding.UTF8.GetString(Thing2.ThingData)); Writeln(Format(''Type %d'', [Thing2.ThingType])); Writeln(TEncoding.UTF8.GetString(Thing1.ThingData)); Writeln(Format(''Type %d'', [Thing1.ThingType])); ReadLn; except on E: Exception do Writeln(E.ClassName, '': '', E.Message); end; end.


El problema se puede ver fácilmente habilitando los punteros de tipo revisado. Agregue esto a la parte superior de su código:

{$TYPEDADDRESS ON}

La documentation dice:

La directiva $ T controla los tipos de valores de puntero generados por el operador @ y la compatibilidad de los tipos de puntero.

En el estado {$ T-}, el resultado del operador @ es siempre un puntero sin tipo (Puntero) que es compatible con todos los demás tipos de punteros. Cuando se aplica @ a una referencia de variable en el estado {$ T +}, el resultado es un puntero escrito que es compatible solo con el puntero y con otros punteros al tipo de la variable.

En el estado {$ T-}, los distintos tipos de punteros que no sean Pointer son incompatibles (incluso si son punteros al mismo tipo). En el estado {$ T +}, los punteros al mismo tipo son compatibles.

Con ese cambio tu programa no se compila. Esta línea falla:

FData := @FBuf[1];

El mensaje de error es:

E2010 Tipos incompatibles: ''System.TArray<System.Byte>'' y ''Pointer''

Ahora, FData es de tipo TArray<Byte> pero @FBuf[1] no es una matriz dinámica sino un puntero a un byte en medio de una matriz dinámica. Los dos no son compatibles. Al operar en el modo predeterminado, donde los punteros no se verifican, el compilador le permite cometer este terrible error. Bastante por qué este es el modo predeterminado es completamente más allá de mí.

Una matriz dinámica es más que un puntero al primer elemento: también hay metadatos, como la longitud y el recuento de referencias. Los metadatos se almacenan en un desplazamiento desde el primer elemento. Por lo tanto, todo su diseño es defectuoso. Almacene el código de tipo en una variable separada, y no como parte de la matriz dinámica.


Los arreglos dinámicos son punteros internamente y compatibles con la asignación de los punteros; pero los únicos punteros correctos en el lado derecho de la asignación son nil u otra matriz dinámica. FData := @FBuf[1]; obviamente está mal, pero es interesante que FData := @FBuf[0]; es probable que esté bien, incluso si $TYPEDADDRESS está habilitado.

El siguiente código compila y funciona como se espera en Delphi XE:

program Project19; {$APPTYPE CONSOLE} {$TYPEDADDRESS ON} uses SysUtils; procedure Test; var A, B: TBytes; begin A:= TBytes.Create(11,22,33); B:= @A[0]; Writeln(B[1]); end; begin try Test; readln; except on E: Exception do Writeln(E.ClassName, '': '', E.Message); end; end.

Parece que el compilador "sabe" que @A[0] es una matriz dinámica, no solo un puntero.


constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes); var Buffer : array of Byte; begin SetLength(Buffer, Length(AThingData) + Sizeof(AThingType)); Buffer[0] := AThingType; Move(AThingData[0], Buffer[1], Length(AThingData)); SetLength(FBuf, Length(Buffer)); Move(Buffer[0], FBuf[0], Length(Buffer)); SetLength(FData, Length(AThingData)); Move(Buffer[1], FData[0], Length(AThingData)); end;