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
Assigned Routine Tests for a nil (unassigned) pointer or procedural variable.
Unit System
Syntax
[
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.
In message <4540f380.53de8de6@midnightbeach.com>, Jon Shemitz
> 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;
No comments:
Post a Comment