¿Cómo se pueden liberar correctamente los registros que contienen varios tipos en Delphi a la vez?

type TSomeRecord = Record field1: integer; field2: string; field3: boolean; End; var SomeRecord: TSomeRecord; SomeRecAr: array of TSomeRecord; 

Este es el ejemplo más básico de lo que tengo y dado que quiero reutilizar SomeRecord (con ciertos campos que permanecen vacíos, sin liberar todo, algunos campos se trasladarían cuando reutilizo SomeRecord , lo que obviamente no es deseado) Estoy buscando un forma de liberar todos los campos a la vez. Empecé con string[255] y usé ZeroMemory() , que estuvo bien hasta que comenzó a filtrar memoria, eso fue porque cambié a la string . Aún me falta el conocimiento para saber por qué, pero parece estar relacionado con que sea dynamic. También estoy usando arreglos dynamics, así que supongo que probar ZeroMemory() en cualquier cosa dinámica daría lugar a fugas. Un día desperdiciado descifrando eso. Creo que resolví esto al usar Finalize() en SomeRecord o SomeRecAr antes de ZeroMemory() , pero no estoy seguro de si este es el enfoque correcto o si solo soy estúpido.

Entonces la pregunta es: ¿cómo liberar todo a la vez? ¿Existe algún procedimiento único para esto de lo que no tengo conocimiento?

En una nota diferente, como alternativa, estaría abierto a sugerencias sobre cómo implementar estos registros de manera diferente para empezar, así que no necesito hacer bashs complicados para liberar cosas. He buscado crear registros con New() y luego deshacerme de Dispose() , pero no tengo idea de qué significa cuando una variable después de una llamada a Dispose() no está definida, en lugar de nil. Además, no sé cuál es la diferencia entre una variable de cierto tipo ( SomeRecord: TSomeRecord ) versus una variable que apunta a un tipo ( SomeRecord: ^TSomeRecord ). Estoy investigando los problemas anteriores en este momento, a menos que alguien pueda explicarlo rápidamente, podría llevar algo de tiempo.

Suponiendo que tiene una versión de Delphi que admite la implementación de métodos en un registro, puede borrar un registro como este:

 type TSomeRecord = record field1: integer; field2: string; field3: boolean; procedure Clear; end; procedure TSomeRecord.Clear; begin Self := Default(TSomeRecord); end; 

Si su comstackdor no es compatible con Default entonces puede hacer lo mismo simplemente de esta manera:

 procedure TSomeRecord.Clear; const Default: TSomeRecord=(); begin Self := Default; end; 

Es posible que prefiera evitar la mutación de un tipo de valor en un método. En ese caso, cree una función que devuelva un valor de registro vacío y utilícelo con el operador de asignación:

 type TSomeRecord = record // fields go here class function Empty: TSomeRecord; static; end; class function TSomeRecord.Empty: TSomeRecord; begin Result := Default(TSomeRecord); end; .... Value := TSomeRecord.Empty; 

Como Default(TypeIdentifier) , no puedo encontrar ninguna referencia de documentación para Default(TypeIdentifier) . ¿Alguien sabe dónde se puede encontrar?


En cuanto a la segunda parte de su pregunta, no veo ninguna razón para no seguir usando registros y asignarlos mediante matrices dinámicas. Intentar administrar la vida usted mismo es mucho más propenso a errores.

¡No hagas que pensar sea demasiado complicado!

La asignación de un record “predeterminado” es solo una pérdida de potencia y memoria de la CPU.

Cuando se declara un record dentro de un TClass , se llena con cero, por lo que se inicializa. Cuando se asigna en la stack, solo se inicializan las variables contadas de referencia: otras clases de variables (como enteros o dobles o booleanos o enumeraciones) están en un estado aleatorio (probablemente distinto de cero). Cuando se asigne en el montón, getmem no inicializará nada, allocmem completará todo el contenido con cero y el new inicializará solo los miembros contados por referencia (como en la inicialización de la stack): en todos los casos, debe usar dispose , finalize+freemem para liberar un record asignado en el montón.

Entonces, acerca de su pregunta exacta, su propia suposición era correcta: para restablecer un contenido de registro después de su uso, nunca use ” fillchar ” (o ” zeromemory “) sin una ” finalize ” previa. Esta es la forma correcta y más rápida:

 Finalize(aRecord); FillChar(aRecord,sizeof(aRecord),0); 

Una vez más, será más rápido que asignar un registro predeterminado. Y en todo caso, si usa Finalize , incluso varias veces, no perderá ningún tipo de memoria: ¡garantía de devolución del 100% del dinero!

Editar: Después de mirar el código generado por un aRecord := default(TRecordType) , el código está bien optimizado: de hecho es un conjunto Finalize + de stosd para emular FillChar . Entonces, incluso si la syntax es una copia / asignación ( := ), no se implementa como una copia / asignación. Mi error aquí

Pero todavía no me gusta el hecho de que a := tenga que ser usado, donde Embarcadero debería haber usado un método de record como aRecord.Clear como syntax, al igual que las matrices dinámicas de DelphiWebScript . De hecho, esto := syntax es exactamente la misma que usa C #. Parece que Embacardero simplemente imita la syntax de C # en todas partes, sin descubrir que esto es extraño . ¿Cuál es el punto si Delphi es solo un seguidor y no implementa piensa “a su manera”? La gente siempre preferirá el C # original a su antecesor (Delphi tiene el mismo padre).

La solución más simple que pienso será:

 const EmptySomeRecord: TSomeRecord = (); begin SomeRecord := EmptySomeRecord; 

Pero para abordar todas las partes restantes de su pregunta, tome estas definiciones:

 type PSomeRecord = ^TSomeRecord; TSomeRecord = record Field1: Integer; Field2: String; Field3: Boolean; end; TSomeRecords = array of TSomeRecord; PSomeRecordList = ^TSomeRecordList; TSomeRecordList = array[0..MaxListSize] of TSomeRecord; const EmptySomeRecord: TSomeRecord = (); Count = 10; var SomeRecord: TSomeRecord; SomeRecords: TSomeRecords; I: Integer; P: PSomeRecord; List: PSomeRecordList; procedure ClearSomeRecord(var ASomeRecord: TSomeRecord); begin ASomeRecord.Field1 := 0; ASomeRecord.Field2 := ''; ASomeRecord.Field3 := False; end; function NewSomeRecord: PSomeRecord; begin New(Result); Result^.Field1 := 0; Result^.Field2 := ''; Result^.Field3 := False; end; 

Y luego aquí algunos ejemplos múltiples sobre cómo operar con ellos:

 begin // Clearing a typed variable (1): SomeRecord := EmptySomeRecord; // Clearing a typed variable (2): ClearSomeRecord(SomeRecord); // Initializing and clearing a typed array variabele: SetLength(SomeRecords, Count); // Creating a pointer variable: New(P); // Clearing a pointer variable: P^.Field1 := 0; P^.Field2 := ''; P^.Field3 := False; // Creating and clearing a pointer variable: P := NewSomeRecord; // Releasing a pointer variable: Dispose(P); // Creating a pointer array variable: ReallocMem(List, Count * SizeOf(TSomeRecord)); // Clearing a pointer array variable: for I := 0 to Count - 1 do begin Pointer(List^[I].Field2) := nil; List^[I].Field1 := 0; List^[I].Field2 := ''; List^[I].Field3 := False; end; // Releasing a pointer array variable: Finalize(List^[0], Count); 

Elige y / o combina como quieras.

Con SomeRecord: TSomeRecord , SomeRecord será una instancia / variable del tipo TSomeRecord . Con SomeRecord: ^TSomeRecord , SomeRecord será un puntero a una instancia o variable de tipo TSomeRecord . En el último caso, SomeRecord será un puntero tipeado. Si su aplicación transfiere una gran cantidad de datos entre rutinas o interactúa con una API externa, se recomienda el puntero tipeado.

new() y dispose() solo se usan con punteros tipeados. Con punteros tipeados, el comstackdor no tiene el control / conocimiento de la memoria que su aplicación está utilizando con este tipo de vars. Depende de usted liberar la memoria utilizada por punteros tipeados.

Por otro lado, cuando se usan variables normales, dependiendo del uso y la statement, el comstackdor liberará la memoria utilizada cuando considera que ya no son necesarias. Por ejemplo:

 function SomeStaff(); var NativeVariable: TSomeRecord; TypedPointer: ^TSomeRecord; begin NaviveVariable.Field1 := 'Hello World'; // With typed pointers, we need to manually // create the variable before we can use it. new(TypedPointer); TypedPointer^.Field1 := 'Hello Word'; // Do your stuff here ... // ... at end, we need to manually "free" // the typed pointer variable. Field1 within // TSomerecord is also released Dispose(TypedPointer); // You don't need to do the above for NativeVariable // as the compiler will free it after this function // ends. This apply also for native arrays of TSomeRecord. end; 

En el ejemplo anterior, la variable NativeVariable solo se usa dentro de la función SomeStaff , por lo que el comstackdor la libera automáticamente cuando finaliza la función. Esta appy para la mayoría de las variables nativas, incluidos los arrays y los registros de “campos”. Los objetos se tratan de manera diferente, pero eso es para otra publicación.