2009-04-23 12 views
26

Me pregunto por qué la mayoría de los ejemplos de Delphi usan FillChar() para inicializar registros.¿Por qué la mayoría de los ejemplos de Delphi utilizan FillChar() para inicializar registros?

type 
    TFoo = record 
    i: Integer; 
    s: string; // not safe in record, better use PChar instead 
    end; 

const 
    EmptyFoo: TFoo = (i: 0; s: ''); 

procedure Test; 
var 
    Foo: TFoo; 
    s2: string; 
begin 
    Foo := EmptyFoo; // initialize a record 

    // Danger code starts 
    FillChar(Foo, SizeOf(Foo), #0); 
    s2 := Copy("Leak Test", 1, MaxInt); // The refcount of the string buffer = 1 
    Foo.s = s2; // The refcount of s2 = 2 
    FillChar(Foo, SizeOf(Foo), #0); // The refcount is expected to be 1, but it is still 2 
end; 
// After exiting the procedure, the string buffer still has 1 reference. This string buffer is regarded as a memory leak. 

Aquí (http://stanleyxu2005.blogspot.com/2008/01/potential-memory-leak-by-initializing.html) es mi nota sobre este tema. OMI, declarar una constante con valor predeterminado es una mejor manera.

Respuesta

34

Motivos históricos, principalmente. FillChar() se remonta a los días de Turbo Pascal y se usó para tales fines. El nombre es realmente un poco inapropiado ya que, si bien dice Fill Char(), realmente es Fill Byte(). La razón es que el último parámetro puede tomar un char o byte. Así que FillChar (Foo, SizeOf (Foo), # 0) y FillChar (Foo, SizeOf (Foo), 0) son equivalentes. Otra fuente de confusión es que a partir de Delphi 2009, FillChar aún solo llena bytes, aunque Char es equivalente a WideChar. Mientras examinamos los usos más comunes de FillChar para determinar si la mayoría de la gente usa FillChar para llenar la memoria con datos de caracteres o simplemente usarla para inicializar la memoria con un determinado valor de bytes, encontramos que fue el último caso el que predominó en su uso en lugar de lo primero. Con eso, decidimos mantener FillChar byte-centric.

Es cierto que borrar un registro con FillChar que contiene un campo declarado utilizando uno de los tipos "gestionados" (cadenas, variantes, interfaz, matrices dinámicas) puede ser inseguro si no se usa en el contexto apropiado.En el ejemplo que proporcionó, sin embargo, es realmente seguro llamar a FillChar en la variable de registro declarada localmente , siempre que sea lo primero que haga en el registro dentro de ese alcance. La razón es que el compilador ha generado código para inicializar el campo de cadena en el registro. Esto ya habrá establecido el campo de cadena en 0 (nil). Llamar a FillChar (Foo, SizeOf (Foo), 0) simplemente sobrescribirá el registro completo con 0 bytes, incluido el campo de cadena que ya es 0. Usando FillChar en la variable de registro después de se asignó un valor al campo de cadena, es no recomendado. Usar su técnica de constante inicializada es una muy buena solución a este problema porque el compilador puede generar el código adecuado para garantizar que los valores de registro existentes se finalicen correctamente durante la asignación.

+2

>>> No se recomienda el uso de FillChar en la variable de registro después de asignar un valor al campo de cadena En tales casos, debe utilizar Finalize (V); FillChar (V, SizeOf (V), 0); Si haces esto con frecuencia, puedes escribir una nueva función, que finaliza + FillChar. Creo que usar FillChar será más rápido que asignar a una constante, pero probablemente hoy no sea relevante. Más importante aún: no necesita declarar una constante para cada tipo de registro. – Alex

+1

>> Creo que usar FillChar será más rápido que asignar a una constante, pero probablemente hoy no sea relevante. Más importante aún: no necesita declarar una constante para cada tipo de registro. << Es por eso que considero esto como un abuso. Sería genial, si el compilador admite la palabra de reserva "nulo", para que podamos escribir "Foo: = void"; para inicializar un registro. – stanleyxu2005

+0

lo uso siempre que puedo ya que me ayuda a seguir el principio de abierto/cerrado. luego, cuando un registro tiene agregado un miembro nuevo (sin cadena), no es necesaria una inicialización adicional. –

2

Tradicionalmente, un personaje es un solo byte (ya no es cierto para Delphi 2009), por lo que usar FILLCHAR con un # 0 que inicializar la memoria asignada de forma que sólo contenía nulos, o el byte 0, o bin 00000000.

En su lugar, debe utilizar la función ZeroMemory para compatibilidad, que tiene los mismos parámetros de llamada que el viejo fillchar.

+0

¿Por qué sigues respondiendo las mismas preguntas que yo al mismo tiempo? Esta vez me ganaste por 35 segundos. –

+0

debe ser algo en el agua. – skamradt

+0

FillChar funciona igual que siempre. Su nombre ya no es tan apropiado, pero aparte de eso, no hay diferencia. –

4

FillChar se utiliza generalmente para llenar Arrays o registros con sólo tipos numéricos y matriz. Tiene razón en que no debe usarse cuando hay strings (o cualquier variable ref-contada) en el registro .

A pesar de su sugerencia de utilizar un const para inicializar que iba a funcionar, un problema entra en juego cuando tengo una longitud variable gama que quiero para inicializar.

9

FillChar está bien para asegurarse de que no obtiene ningún tipo de basura en una estructura nueva, no inicializada (registro, búfer, arrray ...).
No debe utilizarse para "restablecer" los valores sin saber lo que está restableciendo.
No es más que escribir MyObject := nil y esperamos evitar una pérdida de memoria.
En particular, todos los tipos administrados deben observarse cuidadosamente.
Ver la función Finalizar.

Cuando tiene la capacidad de juguetear directamente con la memoria, siempre hay una manera de dispararse en el pie.

+0

me pasó, antes, a veces no usaba fillchar para mi registro, y algunos miembros de ese registro deberían recibir el hilo de basura, ya que siempre uso fillchar o zeromemory. – avar

+0

A Francois: No solicité confirmar este problema. Entiendo que no hay problema si se usa/cuidadosamente /. Me pregunto por qué FillChar se ha utilizado durante tantos años para iniciar registros. – stanleyxu2005

1

Esta pregunta tiene una implicación más amplia que he tenido en mente durante mucho tiempo. Yo también fui educado usando FillChar para los registros. Esto es bueno porque a menudo agregamos nuevos campos al registro (de datos) y, por supuesto, FillChar (Rec, SizeOf (Rec), # 0) se encarga de tales nuevos campos. Si 'lo hacemos correctamente', tenemos que recorrer todos los campos del registro, algunos de los cuales son tipos enumerados, algunos de los cuales pueden ser registros y el código resultante es menos legible y posiblemente erróneo si no agregamos nuevos registre los campos con diligencia. Los campos de cadena son comunes, por lo que FillChar es un no-no ahora. Hace unos meses, di la vuelta y convertí todos mis FillChars en registros con campos de cadena para iterar el borrado, pero no estaba contento con la solución y me pregunto si hay una manera ordenada de hacer el tipo 'Rellenar' simplemente (ordinal/flotar) y 'Finalizar' en variantes y cadenas?

+1

La función 'Finalizar' ayuda. Pero aún sugiero hacerlo correctamente asignándolo a un registro vacío const. Una vez que haya cambiado la estructura de su registro, el compilador le recordará que actualice también este registro vacío. – stanleyxu2005

+0

Brian, mira mi respuesta y sigue el enlace. Esta es la respuesta clara que está buscando :) –

+0

Finalize + ZeroMemory/FillChar cubrirá todos los casos y satisfará todas las necesidades. Y probablemente sea más rápido que asignar a un registro vacío constante. – Fr0sT

12

Si tiene Delphi 2009 y, posteriormente, utilizar la llamada Default para inicializar un registro.

Foo := Default(TFoo); 

Ver David's answer a la pregunta How to properly free records that contain various types in Delphi at once?.

Editar:

La ventaja de usar la llamada Default(TSomeType), es que el registro esté finalizado antes de que se haga efectivo. No hay pérdidas de memoria ni llamadas peligrosas explícitas de bajo nivel a FillChar o ZeroMem. Cuando los registros son complejos, tal vez contengan registros anidados, etc., se elimina el riesgo de cometer errores.

Su método para inicializar los registros se puede hacer aún más sencilla:

const EmptyFoo : TFoo =(); 
... 
Foo := EmptyFoo; // Initialize Foo 

veces desea un parámetro a tener un valor no predeterminado, y luego hacerlo de esta manera:

const PresetFoo : TFoo = (s : 'Non-Default'); // Only s has a non-default value 

Esta voluntad ahorre algo de tipeo y el enfoque se centra en las cosas importantes.

+0

Gracias por su información. OMI, para los viejos compiladores delphi debemos definir algunas constantes de registro predeterminadas en lugar de usar la función mágica FillChar. Para los compiladores Delphi modernos, también debemos declarar constructores para los tipos de registros. Si tiene intereses, puede consultar los proyectos de código abierto desde mi página de perfil. Codifico estos proyectos de acuerdo con el principio que señalé en el comentario. – stanleyxu2005

+0

Se agregaron algunas simplificaciones para definir los registros de const. –

+1

Gracias por la maravillosa() construcción, no tengo idea de que Delphi pueda hacerlo. – Fr0sT

Cuestiones relacionadas