8

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?

Johan
  • 74,508
  • 24
  • 191
  • 319
Jeff Cope
  • 781
  • 2
  • 7
  • 15
  • 8
    Your understanding is wrong. With the exception of managed types (like string), local variables are not initialized. An object is not a managed type. Please provide a citation for "the documented behavior" that states otherwise. – Ken White Apr 16 '14 at 17:20
  • @KenWhite We are coming from Delphi 7 where it did seem to default objects to nil. Now when recompiling in XE2 we end up with AV because our check for nil fails. – Jeff Cope Apr 16 '14 at 17:25
  • Also that seems to conflict with the answer to this thread:http://stackoverflow.com/questions/132725/are-delphi-variables-initialized-with-a-value-by-default – Jeff Cope Apr 16 '14 at 17:31
  • No, it doesn't conflict with that answer at all. See the fourth bullet point: "Local non reference-counted* variables are uninitialized so you have to assign a value before you can use them." A normal variable such as your example is a "local non reference-counted" variable, and the quote clearly states they are **unitialized so you have to assign a value before you can use them**. Comparing to `nil` qualifies as *using them*. D7's behavior was actually less predictable; a couple of things are initialized in more recent versions that were not in D7 and earlier versions. – Ken White Apr 16 '14 at 17:34
  • Why the inconsistency between Delphi 7? How about XE2 64 bit debug build? – Jeff Cope Apr 16 '14 at 17:36
  • From the XE5 help (including the typo in Wiin32): 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 Wiin32 platform, **the contents of a local variable are undefined until a value is assigned to them**. There is no inconsistency in that regard - local variables to objects were not initialized in D7 any differently than they are now. They were **not initialized** in D7, and they're **not initialized** in later versions. " – Ken White Apr 16 '14 at 17:38
  • For the XE5 docs I mentioned, see [here](http://docwiki.embarcadero.com/RADStudio/XE5/en/Variables), toward the end of the "Declaring Variables" section. – Ken White Apr 16 '14 at 17:41
  • Got it, thanks. Just to clarify on the 64 bit debug build from my documented test. It always defaults local TObject variables to nil because of a fluke with the compiler? – Jeff Cope Apr 16 '14 at 17:46
  • It may have something to do with how the debug code is generated, but it shouldn't matter at all. Since you know that local variables are not initialized by default, you should always make sure they're initialized before using them. It's impossible not to do so; you're either making an explicit assignment yourself (`MyObj := TSomething.Create;`) or assigning something created elsewhere (`MyObj := SomeParam` or `MyObj := SomeFunctionResult`. An unnecessary initialization won't hurt (eg., `MyString := ''` won't hurt even though string is a managed type and is initialized to `''`). – Ken White Apr 16 '14 at 17:50
  • 3
    For the record, I did **not** downvote your question. – Ken White Apr 16 '14 at 17:58
  • @JeffCope locals end up with whatever value was either in the register or on the stack wherever they were allocated space. If you're sometimes having builds where you end up with a zero, it's probably just a consequence of some prior code having left a zero there. Zeroes show up in code all over the place so it's not an unusual value to have floating around in deallocated space. It should not be expected to be consistent. – J... Apr 16 '14 at 20:25
  • *In Delphi, the documented behavior for variables descending from TObject is a default value of nil.* It would help if you cited that documentation. That might help us understand the source of the confusion. – David Heffernan Apr 17 '14 at 06:58

2 Answers2

12

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');
Community
  • 1
  • 1
Ken White
  • 123,280
  • 14
  • 225
  • 444
3

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.

Johan
  • 74,508
  • 24
  • 191
  • 319
  • Some of this is factually wrong. *By default Win32 zero-initializes these blocks.* No that is not so. *Delphi uses its own memory manager, but they've decided to use the same convention (almost).* No, again not so. Instance are zeroised in `TObject.InitInstance` with an explicit call to `FillChar`. *This makes sense because otherwise you'd have to initialize lots of variables in every constructor you write.* Actually it is done so that you can safely call `Free` on a partially constructed instance. *Windows already zero-initializes the memory blocks involved.* Not so. – David Heffernan Apr 17 '14 at 19:54
  • Thanks David, fixed now. – Johan Apr 19 '14 at 08:12
  • @DavidHeffernan, does this mean that if I have code I want to speed up I can use records/objects (not classes) instead and not incur the fillchar cost? – Johan Apr 19 '14 at 08:14
  • 1
    The heap allocation cost is far greater than the fill cost. Stack allocation is free. – David Heffernan Apr 19 '14 at 08:18