Pages

Advertisement

Wednesday, July 4, 2007

Interesting Nil Reference Quirks in Delphi Code

So here’s an interesting question. Why does the following code not blow up if x is nil?

FreeAndNil(x);

The Free method of TObject that is called by FreeAndNil does the check for nil before doing the destroy. The discussion on the Delphi.non-tech newsgroup on this topic led to some interesting discussion.

Assigned does the same thing as testing for nil (except for method pointers. See Allan Bauer’s post about this). Here is the Delphi help entry on Assigned:

Assigned Routine Tests for a nil (unassigned) pointer or procedural variable.

Unit System

Syntax

[Delphi] function Assigned(var P: Type): Boolean;

Description Use Assigned to determine whether the pointer or procedure referenced by P is nil. P must be a variable reference of a pointer or procedural type. Assigned(P) corresponds to the test P<> nil for a pointer variable, and @P <> nil for a procedural variable. Assigned returns false if P is nil, true otherwise.

Note:

Assigned can't detect a dangling pointer--that is, one that isn't nil but no longer points to valid data. For example, in the code example for Assigned, Assigned won't detect the fact that P isn't valid.

In the FreeAndNil method you find this code:

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
 

In the TObject.Free method you find this code:

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;
 

That means you can call free on a nil reference and it will NOT give you an access violation, or try to free the object a second time. Since you can call Free on a nil reference, you can also call FreeAndNil on a nil reference. Furthermore you can call it over and over again if you want. The following code never produces any access violations or invalid pointer operations:

T: TObject;

T := nil;

T.Free;

FreeAndNil(T);

T.Free;

After the variable is set to nil, no combination of Free or FreeAndNil will cause any problems whatsoever. So there is no need to test for nil before freeing a variable. It is a waste of time. The two following lines are exactly the same:

FreeAndNil(T);

If Assigned(T) then FreeAndNil(T);

If you don’t believe it, try it yourself. The key is in one line of the TObject.Free method, that might look odd. You can actually use this same odd code in your own code to make nil-safe objects, but we’ll get to that in a moment. Here we go:

if Self <> nil then

That line of code inside the Free method of TObject keeps the method from AVing, even though it looks from the calling code like an AV might be inevitable. Surely, some of you must be thinking that T.Free will AV long before it gets into the Free method because T is itself nil. But it doesn’t. Why is this? Now we are getting to the part I was re-learning. Delphi book author Jon Shemitz explained it in a newsgroup post when he said:

In message <4540f380.53de8de6@midnightbeach.com>, Jon Shemitz wrote:

> theo wrote:

>

> > if Self <> nil then ..

> >

> > Can somebody explain? How can you call MyObject.free when MyObject is nil?

>

> It's pretty simple, really: TObject.Free is not virtual, so MyObject

> is not referenced when you call Free. Rather, the object code simply

> reads the current value, and passes it to the method as the Self

> parameter.

>

> You can call any instance method with a Nil reference, and everything

> will be fine until the method refers to an instance field or calls a

> virtual method, at which point you are dereferencing Nil, and you'll

> get an exception.

>

> By contrast, when you try to call a virtual method using a Nil

> reference, you get an exception at the call point.

So, the explanation is that because TObject.Free is NOT a virtual method, it therefore can be safely called from a nil reference. This is very interesting. I wrote a little bit of code to demonstrate this principle:

unit Unit6;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;
 
type
  TForm6 = class(TForm)
    Button: TButton;
    procedure ButtonClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
    TFunWithNils = class
  private
    FProtectedProperty: Integer;
    function GetProtectedProperty: Integer;
    procedure SetProtectedProperty(const Value: Integer);
  public
    ADataField: integer;
    procedure NonVirtualMethodAccessingDataField;
    procedure IsAVirtualMethod; virtual;
    procedure NotAVirtualMethod_Protected;
    property ProtectedProperty: Integer read GetProtectedProperty write
        SetProtectedProperty;
  end;
 
 
var
  Form6: TForm6;
 
implementation
 
{$R *.dfm}
 
procedure TForm6.ButtonClick(Sender: TObject);
var T: TFunWithNils; W: integer;
begin
  T := TFunWithNils.Create;
  try
    T.NonVirtualMethodAccessingDataField;
    T.IsAVirtualMethod;
    T.ADataField := 5;
    FreeAndNil(T);
    T.NotAVirtualMethod_Protected; //NEVER AV'S
    T.ProtectedProperty := 7;      //NEVER AV'S
    W := T.ProtectedProperty;      //NEVER AV'S
    FreeAndNil(T);                 //NEVER AV'S
    T.IsAVirtualMethod;            //ALWAYS AV'S RIGHT HERE
    T.NonVirtualMethodAccessingDataField; //ALWAYS AV'S INSIDE METHOD CALLED
    T.ADataField := 5;             //ALWAYS AV'S RIGHT HERE
  finally
    T.Free; //<----never AV's here because T is already nil by this point
  end;
end;
 
{ TFunWithNils }
 
function TFunWithNils.GetProtectedProperty: Integer;
begin
  if self <> nil then
    Result := FProtectedProperty
  else result := 0;
end;
 
procedure TFunWithNils.IsAVirtualMethod;
begin
  if Self = nil then exit;
  ADataField := 5;
end;
 
procedure TFunWithNils.NonVirtualMethodAccessingDataField;
begin
  ADataField := 5;
end;
 
procedure TFunWithNils.NotAVirtualMethod_Protected;
begin
  if Self = nil then exit;
  ADataField := 5;
end;
 
procedure TFunWithNils.SetProtectedProperty(const Value: Integer);
begin
  if self = nil then
    exit;
  FProtectedProperty := Value;
end;
 
end.
 

I’d advise playing with this code a bit, commenting out lines that get AV’s to see the next ones that get AV’s. You’ll discover that the rules are

1) nil references to virtual methods AV right at the point of access

2) nil references to static methods AV inside the static methods as soon as they access a data field or a virtual method of the class

3) nil references can always be freed without consequence

4) nil references to a data field AV right at the point of access

If you think about the consequences of this a little bit you will soon realize that one could construct a class that is nil-proof, meaning that you could dereference nil references to this class without ever getting any AV’s. The way you would do it is by using only non-virtual methods, each of which has a

  if self = nil then
    exit;

as the first line. For data, you would use properties whose accessor and getter methods are protected in the same manner as the class above. The key is to write it so that no fields or virtual methods are accessed until after self is tested for nil. Then, as long as you nil a reference once you’ve freed it, you would not have to worry about double-frees, AV’s from dereferencing nils, etc. Dangling pointers are still a possible problem though, if you make copies of pointers. The only real cure for them is to either disallow copies of references or require them to always be passed as var parameters to procedures, so that when they are FreeAndNil’led in the procs, they are thereby nilled in the calling code as well.

No comments:

Post a Comment