tags:

views:

1628

answers:

2

In Delphi, why does the Assigned() function still return True after I call the destructor?

The below example code will write "sl is still assigned" to the console.

However, I can call FreeAndNil(sl); and it won't be assigned.

I've been programming in Delphi for a while, but this never made sense to me.

Can someone explain?

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.

I tried comparing the VCL operations... FreeAndNil is short and sweet and makes sense:

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

But TObject.Free is in mysterious assembler, which I don't understand:

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

If you use sl.Free, the object is freed but the variable sl still points to the now invalid memory.

Use FreeAndNil(sl) to both free the object and clear the pointer. Besides FreeAndNil is threadsafe so it is not possible that another thread accesses the invalid pointer.

By the way, if you do:

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;
Gamecat
Are you *sure* it's threadsafe? vmtDestroy could be easily called twice on the same object, so hopefully that *is* threadsafe.
Roddy
A single call to FreeAndNil is threadsafe. As are two calls. Note that the parameter is a var parameter and the pointer is first copied, then set to nil, and then freed. And note that free exits if the pointer is nil.
Gamecat
As written, in the original question, FreeAndNil() is unfortunately *not* threadsafe, because two threads could each copy the pointer then both proceed to free the object. FreeAndNil() should really use Interlocked Variable Access ( http://msdn.microsoft.com/en-us/library/ms684122(VS.85).aspx ) .
Mattias Andersson
Calling FreeAndNil thread-safe really mars an otherwise good answer. If you're even at a point where you think you NEED thread-safety while freeing a shared object, you've got bigger problems in your code than you could ever hope for FreeAndNil to solve.
Rob Kennedy
@Rob Kennedy, agreed. But I have been maintaining other peoples code for too long to be afreaid of those problems.
Gamecat
+7  A: 

Delphi VCL 'objects' are actually always pointers to objects, but this aspect is typically hidden from you. Just freeing the object leaves the pointer dangling around, so you should use FreeAndNil instead.

The "Mysterious Assembler" translates roughly to:

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

Because Free checks for NIL first, it's safe to call FreeAndNil multiple times...

Roddy