Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Assigned return true for uninitialized variables?

I read many posts on forum about pointers, Assigned function, Free function, FreeAndNil function, etc... I already know Free function don't remove the pointer reference to an object assigned and FreeAndNil does it... All posts I read treat this subject considering Create method already was executed, or in other words, considering an object already created.

My question is: Why Assigned function returns true for a uninitialized object variable ?

Follow an example:

procedure TForm1.FormCreate(Sender: TObject);
var
  Qry: TADOQuery;    
begin
  if Assigned(Qry) then
    ShowMessage('Assigned')
  else
    ShowMessage('Unassigned');

  Qry := TADOQuery.Create(nil);

  if Assigned(Qry) then
    ShowMessage('Assigned')
  else
    ShowMessage('Unassigned');
end;

That example displays 'Assigned' twice!

Conclusion: Immediately after Qry has been declared and before its create method has been executed the pointer to Qry isn't NIL !

If I put Qry := nil; at the first line into procedure above everything works fine... it displays 'Unassigned' and 'Assigned'.

Why??

Is there any safe way to know if a class variable already has its create method executed?

like image 414
user3202858 Avatar asked Jan 16 '14 17:01

user3202858


3 Answers

Your variable is a local variable and so is not initialized. It could contain any value.

The documentation says:

On the Win32 platform, the contents of a local variable are undefined until a value is assigned to them.

Note that, as an implementation detail, some types are managed and even local variables of managed types are initialized. Examples of managed types include: strings, interfaces, dynamic arrays, anonymous types and variants.

You ask:

Is there any safe way to know if a class variable already has its create method executed?

If that variable is a local variable, the answer is no. The onus falls to you the programmer. In practice it is seldom an issue because good code has short procedures which makes it harder for you to slip up. And even if you do the compiler will invariably warn you.

Other types of variables like class fields and global variables are initialized.

like image 92
David Heffernan Avatar answered Sep 21 '22 12:09

David Heffernan


Because when creating a pointer, it cames with whatever garbage value was in that memory position. If you want to write NIL in it, it takes some CPU cycles, and I think it's not automatically done by Delphi because you may want something faster. In your example, why assign NIL to a variable, if soon afterwards you're going to put another value in it?

like image 23
Rodrigo Avatar answered Sep 20 '22 12:09

Rodrigo


From the documentation of the Assigned function (emphasis mine):

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.

The Assigned function is effectively implemented as:

function Assigned(const P): Boolean;
begin
  Result := Pointer(P) <> nil;
end;

So the function isn't really checking whether the value truly is assigned. Rather it's checking a side-effect of being assigned.

  • As a result the function is guaranteed to return True if it is assigned.
  • But behaviour is undefined if the value is uninitialised. Basically since an uninitialised value has a garbage value left over from previous operations, it might be nil, or if might not.

Another thing to note is that Assigned has no way to determine the validity of its value. E.g. The following call to Assigned returns True even though the underlying object is no longer valid.

var
  LObject: TObject;
begin
  LObject := TObject.Create;
  LObject.Free;
  if Assigned(LObject) then ShowMessage('Still assigned!?');
end;

EDIT: Addendum

In response to the second part of your question.

Is there any safe way to know if a class variable already has its create method executed?

There is no safe way to determine if an object instance has been created. (There's also no way to reliably confirm that it hasn't already been destroyed.)

However, there are conventions (and good practices) you can follow to help you on the way.

First note that you should only be "unsure" if something was created if it's a deliberate feature of that piece of code. E.g. If you intend an object to be "lazy initialised".

  • What I'm trying to say here is: Never check Assigned just because you're worried that there might be a bug that prevents it from being assigned.
  • Not only is this impossible to do reliably, but you overcomplicate your code... Which increases the chance of bugs.
  • Also if you find something is unexpectedly not Assigned, then what can you do about it? Ignoring it would simply be pointless. Also, it's no good saying: "Ok, then I'll create the object". Because then you're duplicating creation logic in multiple places.
  • Basically you should try to make every part of your program correct - not have your program try to double-check itself everywhere.

So now that we're (hopefully) agreed that you only check if something is created if you've deliberately chosen that being created is optional. You do this as follows:

  • At first opportunity, ensure the variable/field reference is initialised to nil. So then it's guranteed to be assigned a value which means the object is not created. (Yes, the naming is a bit warped.)
  • You can set the vairable/field reference to a new instance of an object or set it by copying another reference of an already existing object. (Note the existing refernce might also be nil, but that doesn't cause any problems.)
  • If you ever destroy the object (or even just want to stop using it from that reference), set your variable/field reference to nil again.
  • NOTE: Delphi already initialises the member fields of a new class. So those won't need special attention.
like image 29
Disillusioned Avatar answered Sep 20 '22 12:09

Disillusioned