2008-12-12 12 views
8

En mi Delphi7 este código¿Qué se supone que ocurre al usar un objeto después de FreeAndNil?

var MStr: TMemoryStream; 
... 
FreeAndNil(MStr); 
MStr.Size:=0; 

genera un AV: violación de acceso en la dirección 0041D6D1 en el módulo 'Project1.exe'. Lectura de la dirección 00000000. Pero alguien insiste en que no debe plantear ninguna excepción, pase lo que pase. También dice que su Delphi 5 no plantea excepciones. Él llama a esto un "error del puntero rancio". En otras palabras, dice que FreeAndNil no se puede usar como depurador para detectar un intento doble de liberar un objeto o usar un objeto liberado.

¿Alguien me puede aclarar? ¿Debería aumentar y fallar (siempre/aleatoriamente) o el programa debería ejecutar este error sin problemas?

Gracias


lo pregunto porque creo que tengo un "objeto de doble liberación" o error "acceso libre y re" en mi programa. ¿Cómo puedo llenar la memoria asignada a un objeto con ceros DESPUÉS de haber liberado el objeto? Quiero esta forma de detectar dónde está el error, obteniendo AV. Inicialmente, esperaba que si establecía el objeto en FreeAndNil, SIEMPRE obtendría un AV cuando intentaba volver a acceder a él.

+2

creo que 'alguien' le faltan algunos conceptos importantes. –

+1

Como dije antes, FastMM tiene una opción que hace lo que usted describe. Rob señala que sobrescribirá la memoria con "números mágicos" para que pueda detectar este tipo de errores. Mira esto. Descargue FastMM4 completo y habilite el registro en el archivo. Lo he encontrado bastante útil. – Vegar

+2

FreeAndNil() no anula la memoria ocupada por la instancia, elimina la referencia pasada. – Bevan

Respuesta

9

Por lo que estoy viendo, este código siempre debería dar como resultado un error. FreeAndNil establece explícitamente ese valor pasado a Nil (también conocido como 0), por lo que debe obtener una violación de acceso al intentar desreferenciar el objeto.

21

Siempre es incorrecto utilizar métodos o propiedades de una referencia nula, incluso si parece funcionar a veces.

FreeAndNil de hecho no se puede utilizar para detectar doble frecuencia. Es seguro llamar al FreeAndNil en una variable que ya no existe. Como es seguro, no ayuda a detectar nada.

Esto no es un error de puntero rancio. Este es un error de referencia nula. Un error de puntero rancio es cuando ha liberado un objeto pero no borró todas las variables que lo hicieron referencia. Entonces la variable aún conserva la dirección anterior del objeto. Esos son muy difíciles de detectar. Usted puede obtener un error tal como esto:

MStr := TMemoryStream.Create; 
MStr.Free; 
MStr.Size := 0; 

También puede obtener uno como este:

MStr := TMemoryStream.Create; 
OtherStr := MStr; 
FreeAndNil(MStr); 
OtherStr.Size := 0; 

Usando MStr.Size después de haber liberado el objeto MStr hace referencia es un error, y se debe plantear una excepción. Si hace generar una excepción depende de la implementación. Tal vez lo haga, y tal vez no sea así. Sin embargo, no es aleatorio.

Si está buscando un error doblemente libre, puede usar los asistentes de depuración que proporciona FastMM, como otros han sugerido también. Funciona al no liberar la memoria de nuevo al sistema operativo, o incluso volver al grupo interno de memoria libre de Delphi. En su lugar, escribe datos mal conocidos en el espacio de memoria del objeto, por lo que cuando vea esos valores, sabrá que está leyendo algo que ya ha liberado. También modifica el VMT del objeto para que la próxima vez que llame a un método virtual en esa referencia de objeto, obtenga una excepción predecible, e incluso le dirá qué objeto supuestamente liberado intentó usar. Cuando intenta liberar el objeto nuevamente, puede indicarle no solo que ya lo liberó, sino también dónde fue liberado la primera vez (con un seguimiento de la pila) y dónde fue asignado.También recopila esa información para informar sobre fugas de memoria, donde liberaste un objeto menos de una vez en lugar de más.

También son hábitos que se pueden utilizar para evitar el problema de futuro código:

  • reducir el uso de variables globales. Una variable global podría ser modificada por cualquier código a lo largo del programa, lo que le obligará a preguntarse cada vez que lo use, "¿El valor de esta variable sigue siendo válido o algún otro código ya lo ha liberado?" Cuando limita el alcance de una variable, reduce la cantidad de código que debe considerar en su programa cuando busca razones por las cuales una variable no tiene el valor que espera.
  • Sea claro sobre a quién pertenece un objeto. Cuando hay dos piezas de código que tienen acceso al mismo objeto, necesita saber cuál de esas piezas de código posee el objeto. Es posible que cada uno tenga una variable diferente para hacer referencia al objeto, pero todavía hay un solo objeto allí. Si un fragmento de código llama al FreeAndNil en su variable, aún deja la variable del otro código sin cambios. Si ese otro código cree que posee el objeto, entonces estás en problemas. (. Este concepto de propietario no está necesariamente ligada a la propiedad TComponent.Owner No tiene por qué ser un objetoque lo posee, sino que podría ser un subsistema general de su programa.)
  • No mantener las referencias a persistentes objetos que no son de su propiedad. Si no mantiene referencias de larga duración a un objeto, entonces no tiene que preocuparse de si esas referencias siguen siendo válidas. La única referencia persistente debe estar en el código que posee el objeto. Cualquier otro código que necesite usar ese objeto debería recibir una referencia como parámetro de entrada, usar el objeto y luego descartar la referencia cuando devuelve su resultado.
5

Si define un puntero a cero, no se debe ser capaz de utilizarlo más. Pero si tiene otro puntero al mismo objeto, puede usarlo sin obtener un AV, ya que este apunta a la dirección del objeto y no a cero.

Además, liberar un objeto no borra la memoria utilizada por ese objeto. Simplemente lo marca como no en uso. Esa es la razón por la que quieres obtener un AV. Si la memoria liberada se asigna para otro objeto, obtendrá un AV, porque ya no contiene datos que parecen válidos.

FastMM4 tiene algunas configuraciones que puede usar durante la depuración, que detectarán tales condiciones. Desde el FsatMM4Options.inc:

{Establecer la siguiente opción de realizar un control exhaustivo de todos los bloques de memoria. Todos los bloques están rellenos con un encabezado y un remolque que se utilizan para verificar la integridad del montón. Los bloques liberados también se borran para garantizar que no puedan reutilizarse después de liberarse. Esta opción ralentiza las operaciones de memoria de forma espectacular y solo debe utilizarse para depurar una aplicación que es sobreescribiendo la memoria o reutilizando punteros liberados. Establecer esta opción habilita automáticamente CheckHeapForCorruption y deshabilita ASMVersion. Muy importante: si habilita esta opción, su aplicación requerirá la biblioteca FastMM_FullDebugMode.dll. Si esta biblioteca no está disponible, obtendrá un error al iniciar .} {
definir $ FullDebugMode}

otra cita del mismo archivo:

FastMM coge siempre intenta liberar el mismo bloque de memoria dos veces ...

usos como Delphi FastMM de Delphi 2007 (2006?), Debería obtener un error si intenta duplicar un objeto.

+0

Libre y nulo también libera el objeto por lo que también fallará otro puntero a él. –

+0

¿Es eso cierto para las versiones delphi más antiguas y para el administrador de memoria clásico, o solo para Delphi 2007+ con FastMM? ¿Cuál sería el propósito del comentario en FastMM4Options.inc si lo que dices es verdad? – Vegar

+0

Gamecat, es muy posible que reutilizar un puntero rancio (no nulo) no genere ninguna excepción (y es posible que funcione bien). Por supuesto, eso es algo malo, y es mil veces mejor si falla , pero al menos en Delphi 5 y 7 puedes reutilizar punteros obsoletos. –

8

Sólo para complicar el asunto:

Si el método se llama a un método (no virtual) estático y no llama a cualquiera de los métodos virtuales en sí ni acceder a cualquiera de los campos del objeto, no obtendrá una infracción de acceso incluso si la referencia del objeto se ha establecido en NIL.

La razón de esto es que la infracción de acceso se produce al eliminar la referencia del auto puntero (en este caso NIL), pero eso solo ocurre al acceder a un campo o al VMT del objeto para llamar a un método virtual.

esto es sólo una excepción a la regla de que no se puede llamar a los métodos de un objeto referencia NIL que me gustaría mencionar aquí.

1

Thomas Mueller: has métodos de clase virtuales? Un constructor es una especie de método virtual, pero lo llamas contra el tipo, no contra la instancia. Esto significa que incluso algunos métodos virtuales específicos no causarán AV en un nulo referencia: D

Vegar: No se podía ser más adecuado! FastMM es la mejor herramienta de todos los tiempos que me ayudó a rastrear este tipo de errores.

+0

Se producirá un error al llamar a un método de clase virtual contra una variable de instancia. Los constructores no tienen nada que ver con eso. –

Cuestiones relacionadas