Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't object default to nil?

Tags:

delphi

In Delphi, the documented behavior for variables descending from TObject is a default value of nil. However, I have come across a situation where this is not the case.

Running the following code sample through the IDE (F9) gives mixed results

var
  objTemp : TMemDataSet;
begin
  if (objTemp = nil) then
     ShowMessage('Nil');
end;
  • 32 Bit / Debug Mode, not defaulted to nil
  • 32 Bit / Release Mode, not defaulted to nil
  • 64 Bit / Debug Mode, is defaulted to nil
  • 64 Bit / Release Mode, not defaulted to nil

My understanding is the value should always default to nil.

Also tested this under XE2 and XE5 with the same results.

Is this the expected behavior in Delphi?

like image 991
Jeff Cope Avatar asked Apr 16 '14 17:04

Jeff Cope


2 Answers

Your understanding is incorrect. Local variables to non-managed types (IOW, non reference-counted types) are not initialized. You have to assign a value to them before you can use them.

From the XE5 documentation (see the bottom portion of the "Declaring Variables" section - I've included type typo in Wiin32, but the emphasis is mine):

If you don't explicitly initialize a global variable, the compiler initializes it to 0. Object instance data (fields) are also initialized to 0. On the Win32 platform, the contents of a local variable are undefined until a value is assigned to them.

Note that whenever Emba writes "Win32" they mean non-ARC compiler, so the above is also valid for Win64 and OSX.

You can find the same information in Delphi 2007 by using the search term Variables in the help index; it's the one between "variables VBScript" and "variables [OpenGL]".

The disparity you're seeing with the Win64 debug build may just be something done by the compiler, a lucky accident, or something else entirely. It shouldn't matter, though. As you know that local variables are not initialized by default, simply make sure you do so in all cases before using them. It's not a difficult rule to enforce; when you declare a local variable,

var
  MyObj: TSomething;

you're either assigning a value yourself, or something you've received from elsewhere in your code:

MyObj := TSomething.Create;   // Created yourself
MyObj := GetSomething();      // Function result
MyObj := Self.SomethingCollection[Self.SomethingCount - 1]; // Local ref

There should be absolutely no reason to need to depend on a local variable being initialized or not, as the test can be done on either the external reference before assignment to the local var, or on the local var after assignment of the external reference:

if SomethingIGot = nil then
  raise Exception.Create('Received a nil parameter');
MyObj := SomethingIGot;

// or

MyObj := SomethingIGot;
if not Assigned(MyObj) then
  raise Exception.Create('MyObj was assigned a nil value');
like image 135
Ken White Avatar answered Sep 20 '22 21:09

Ken White


Ken explained to you the how, let me try and explain the why...

In Delphi, the documented behavior for variables descending from TObject is a default value of nil.

You have something confused here: The member variables of a class (i.e. descendant of TObject) are initialized to 0 or nil.
However whether the Object reference itself is initialized depends on the context.

This has been the case since at least Delphi 2.

The reason for this is a speed optimization:

Local variables are short lived
Local (a global) variables live on the stack.
This memory structure continuously reuses the same memory.
Initializing variables to nil (or 0) would not save you any work, because you're supposed to instantiate the variable to something useful.

procedure Test;
var
  MyObject: TMyObject;
begin   
  MyObject:= TMyObject.Create;  
  .....

Initializing it to nil before the procedure starts obviously serves no purpose here, because you cannot work with it until you've set it to something non-nil.
Because local variables are used close to where they are declared there is little risk of confusion.

When the procedure ends, the local variables go out of scope.
What this really means is that the memory space where these variables used to live is reused in another procedure.

Objects can be long lived
When an object is created, the system allocates memory for it on the heap.
Delphi uses its own memory manager. For all objects, you can be sure that after the call to TObject.Create all member variables of the object are set to 0 (or nil).
As David pointed out, this allows the system to safely free the instance if the Create (the part further down the line from TObject) fails.

It also makes sense because otherwise you'd have to initialize lots of variables in every constructor you write; now you only have to give non-null members a value.

This makes sense on several levels.
Classes can have dozens or hundreds of member variables.
It prevents mistakes.
It allows errors in constructors to be handled.

like image 20
Johan Avatar answered Sep 23 '22 21:09

Johan