24
votes

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;
4
This question shows how programmers could conflate variable names that are in one scope, with objects which exist onthe heap. The object is actually memory on the heap, and Free releases that memory on the heap, but it isn't possible for that method to erase the local variable that contains a REFERENCE to the object, but which is NOT the object itself. Even though the pointer semantics in delphi object references which are pointers to objects are mostly hidden, here's one case where the underlying pointer implementation leaks through.Warren P

4 Answers

37
votes

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.

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;
14
votes

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...

3
votes

The Free method of TObject is like the "delete operator" in C++. Calling free will first call the Destroy function and then it will free the memory block that was allocated for the object. By default the pointer to the memory is not then set to zero because this will use up one instruction.

The correct thing to do in most cases is not to set the pointer to zero because in most cases it does not matter. However, sometimes it is important and so you should only nil the pointer for those cases.

For example. In a function where an object is created and then freed at the end of the function there is no point in setting the variable to zero since that is just wasting cpu time.

But for a global object or field that might be referenced again later you should set it to zero. Use FreeAndNil or just set the pointer to nil yourself, does not matter. But stay away from zeroing variables that do not need to be zeroed by default.

1
votes

We have simple rules:

  1. If you want to use Assigned() to check if an object Obj is already created or not, then make sure you use FreeAndNil(Obj) to free it.

  2. Assigned() only says if an address is assigned or not.

  3. The local object reference is always assigned a garbage address (some random address), so it is good to set it to nil before using it.

Example: (This is not the full code)

{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;