2008-10-24 7 views
24

En Delphi, ¿por qué la función Assigned() aún devuelve True después de llamar al destructor?¿Por qué se asignan objetos Delphi incluso después de llamar a .Free?

El siguiente código de ejemplo escribirá "sl todavía está asignado" a la consola.

Sin embargo, puedo llamar a FreeAndNil (sl); y no será asignado.

He estado programando en Delphi por un tiempo, pero esto nunca tuvo sentido para mí.

¿Alguien puede explicar?

program Project1; 
{$APPTYPE CONSOLE} 
uses SysUtils, Classes; 

var 
    sl : TStringList; 

begin 
    sl := TStringList.Create; 
    sl.Free; 
    if Assigned(sl) then 
    WriteLn('sl is still assigned') 
    else 
    WriteLn('sl is not assigned'); 
end. 

he intentado comparar las operaciones VCL ... FreeAndNil es corto y dulce y tiene sentido:

procedure FreeAndNil(var Obj); 
var 
    P: TObject; 
begin 
    P := TObject(Obj); 
    TObject(Obj) := nil; // clear the reference before destroying the object 
    P.Free; 
end; 

Pero TObject.Free está en ensamblador misteriosa, que no entiendo:

procedure TObject.Free; 
asm 
     TEST EAX,EAX 
     JE  @@exit 
     MOV  ECX,[EAX] 
     MOV  DL,1 
     CALL dword ptr [ECX].vmtDestroy 
@@exit: 
end; 
+9

Esta pregunta muestra cómo los programadores podrían confundir los nombres de variables que se encuentran en un ámbito, con objetos que existen enel montón. El objeto es realmente memoria en el montón, y Free libera esa memoria en el montón, pero no es posible que ese método borre la variable local que contiene una REFERENCIA al objeto, pero que NO es el objeto en sí. A pesar de que la semántica del puntero en las referencias del objeto delphi que son punteros a los objetos está en su mayoría oculta, aquí hay un caso en el que se filtra la implementación del puntero subyacente. –

Respuesta

36

Si usa sl.Free, el objeto se libera pero la variable sl aún apunta a la memoria no válida.

Utilice FreeAndNil (sl) para liberar el objeto y borrar el puntero.

Por cierto, si lo hace:

var 
    sl1, sl2: TStringList; 
begin 
    sl1 := TStringList.Create; 
    sl2 := sl1; 
    FreeAndNil(sl1); 
    // sl2 is still assigned and must be cleared separately (not with FreeAndNil because it points to the already freed object.) 
end; 




procedure TObject.Free; 
asm 
    TEST EAX,EAX 
    JE  @@exit    // Jump to exit if pointer is nil. 
    MOV  ECX,[EAX]   
    MOV  DL,1 
    CALL dword ptr [ECX].vmtDestroy // Call cleanup code (and destructor). 
@@exit: 
end; 
+0

¿Está seguro * de que es seguro para la rosca? vmtDestroy podría ser llamado fácilmente dos veces en el mismo objeto, con la esperanza de que * is * threadsafe. – Roddy

+5

Como está escrito, en la pregunta original, FreeAndNil() desafortunadamente * no * es inseguro, ya que dos subprocesos pueden copiar el puntero y luego ambos proceden a liberar el objeto. FreeAndNil() realmente debería usar acceso de variable interbloqueado (http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx). –

+11

Llamar FreeAndNil thread-safe realmente funciona con una buena respuesta. Si estás incluso en un punto donde crees que NECESITAS seguridad con hilos mientras liberas un objeto compartido, tienes problemas más grandes en tu código de los que podrías esperar que FreeAndNil solucione. –

13

'objetos' Delphi VCL son en realidad siempre punteros a objetos, pero este aspecto es normalmente oculto. Simplemente liberando el objeto deja el puntero colgando, por lo que debería usar FreeAndNil en su lugar.

El "misterioso ensamblador" se podría traducir como:

if Obj != NIL then 
    vmtDestroy(obj); // which is basically the destructor/deallocator. 

Debido cheques gratis para NIL primera, es seguro que llamar varias veces FreeAndNil ...

+3

... y configure el registro DL en 1, para marcar que Destroy fue llamado con este método y llamar al método AfterDestruction asociado. –

3

El método gratuito de TObject es como el " eliminar operador "en C++. Llamar gratis primero llamará a la función Destruir y luego liberará el bloque de memoria que se asignó para el objeto. Por defecto, el puntero a la memoria no se pone a cero porque usará una instrucción.

Lo correcto en la mayoría de los casos es no establecer el puntero a cero porque en la mayoría de los casos no importa. Sin embargo, a veces es importante, por lo que solo debe anular el puntero para esos casos.

Por ejemplo. En una función en la que se crea un objeto y luego se libera al final de la función, no tiene sentido establecer la variable en cero, ya que eso solo está desperdiciando el tiempo de la CPU.

Pero para un objeto o campo global al que se pueda hacer referencia de nuevo más adelante, conviene ponerlo en cero. Use FreeAndNil o simplemente configure el puntero a cero, no importa. Pero manténgase alejado de las variables de ajuste a cero que no necesitan ponerse a cero por defecto.

1

Tenemos reglas simples:

  1. Si desea utilizar Assigned() para comprobar si un objeto Obj ya se crea o no, luego asegurarse de que utiliza FreeAndNil(Obj) para liberarla.

  2. Assigned() solo dice si se asigna o no una dirección.

  3. La referencia de objeto local siempre tiene asignada una dirección de basura (alguna dirección aleatoria), por lo que es bueno establecerla en cero antes de usarla.

Ejemplo: (Este no es el código completo)

{Opened a new VCL application, placed a Button1, Memo1 on the form 
Next added a public reference GlobalButton of type TButton 
Next in OnClick handler of Button1 added a variable LocalButton 
Next in body, check if GlobalButton and LocalButton are assigned} 

    TForm2 = class(TForm) 
    Button1: TButton; 
    Memo1: TMemo; 
    procedure Button1Click(Sender: TObject); 
    private 
    { Private declarations } 
    public 
    { Public declarations } 
    GlobalButton: TButton; 
    end; 

procedure TForm2.Button1Click(Sender: TObject); 
var 
    LocalButton: TButton; 
begin 
    if Assigned(GlobalButton) then 
    Memo1.Lines.Add('GlobalButton assigned'); 
    if Assigned(LocalButton) then 
    Memo1.Lines.Add('LocalButton assigned'); 
end; 
Cuestiones relacionadas