Practically every developer, even a newcomer in programming, can create and destroy objects correctly. A classic construction that is used in the majority of programs looks the following way:
1 2 3 4 5 6 |
MyObject:= TMyClass.Create(); try {some code…} finally MyObject.Free; end; |
Yet, some time ago, there were a lot of discussions where to place object creation: before an exception handler or within the try-finally-end construction. Of course, now, it is not difficult to find an answer to this question. An example of well-written code can be found on many online resources, such as StackOverflow, in books and in official documentation.
But do you really understand why it is so and what can happen if you use the wrong variant? It is possible that you will have to answer this question during a technical interview. If you do not know the correct answer, just keep reading this article and learn how to avoid omitting mistakes when using Windows IDE!
Table of Contents
What is the best way to debug a Delphi code problem?
You can get debug messages in various ways, but one of the most convenient is to use the CodeSite logging system. You can install the ‘lite’ version from GetIt Package Manager using this link: https://getitnow.embarcadero.com/?q=codesite&product=rad-studio&sub=all&sortby=date&categories=-1
What is the wrong way to use an object?
As an example, let’s create a class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
TMyClass = class private function getSelfPointer: Pointer; public constructor Create(beRaized: Boolean = False); procedure Free; overload; end; implementation {$R *.dfm} { TMyClass } constructor TMyClass.Create(beRaized: Boolean); begin if beRaized then raise Exception.Create('Error Message'); end; procedure TMyClass.Free; begin // inherited; CodeSite.Send( 'Destroy object'); {$IFNDEF AUTOREFCOUNT} if Self <> nil then begin CodeSite.Send( ' Destroyed object address:', IntToHex(Integer(Pointer(self)))); Destroy; end; {$ENDIF} end; function TMyClass.getSelfPointer: Pointer; begin Result := Pointer(Self); end; |
In our class we will redefine the Free method only for having a look at the address of the object that we want to destroy. We will copy the code that will destroy the object from the Free method of the TObject class. The only unique method will be getSelfPointer that returns the Pointer to the Object.
The class constructor as a parameter will have a logical meaning. If it equals True, an exception will be generated and an object won’t be created.
Now, let’s take two global variables of the TMyClass type.
1 2 3 4 5 |
var fMain: TfMain; MyObject, MyObject2: TMyClass; |
How do we get an error in a Delphi constructor?
Then let’s place two buttons on the form and define click handlers for them.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
procedure TfMain.Button1Click(Sender: TObject); begin MyObject:= TMyClass.Create(); try CodeSite.Send('Actual MyObject address: ', IntToHex(Integer(MyObject.getSelfPointer ), 8)); finally MyObject.Free; end; MyObject2:= TMyClass.Create(); try CodeSite.Send('MyObject2 was created. Actual MyObject2 address: ', IntToHex(Integer(MyObject2.getSelfPointer ), 8)); // MyObject:= TMyClass.Create(True); try CodeSite.Send('Try creating MyObject'); MyObject:= TMyClass.Create(True); finally CodeSite.Send( 'MyObject. Finally'); MyObject.Free; end; finally end; end; procedure TfMain.Button2Click(Sender: TObject); begin CodeSite.Send( 'MyObject2.ClassName', MyObject2.ClassName ); end; |
To begin with, we create an object MyObject and put its address in the memory to the log and immediately destroy it. Then we create MyObject2, check its address in the log and then create MyObject again. At the same time, we generate an exception in the constructor. Actually, this object won’t be created.
According to the official documentation – https://docwiki.embarcadero.com/RADStudio/Sydney/en/Methods_(Delphi)– “If an exception is raised during execution of a constructor that was invoked on a class reference, the Destroy destructor is automatically called to destroy the unfinished object“.
As a result of the code execution, in the log we see the following:
1 2 3 4 5 6 7 |
Actual MyObject address: = 018A8B80 Destroy object Destroyed object address: = 018A8B80 MyObject2 was created. Actual MyObject2 address: = 018A8B80 Try creating MyObject. MyObject. Finally Destroy object |
At the very beginning, MyObject is created and then it is immediately destroyed. Then MyObject2 is created. And here’s where we have the first surprise. The address of a new object is fully identical to the address of the object that has been destroyed. But, actually, there is nothing unexpected given the peculiarities of how the object storage is organized in Delphi.
Then we need to bear in mind that though the Free method destroys an object, it doesn’t destroy the link that leads to it.
In other words, MyObject won’t be equal to nil.
What happens during the creation of an object placed inside the exception handler?
What happens if now during the creation of the object placed inside the exception handler, an error will arise? You are right, the finally block will be executed and the Free method will be called.
We are talking about this part of the code:
1 2 3 4 5 6 7 8 9 |
try CodeSite.Send('Try creating MyObject'); MyObject:= TMyClass.Create(True); finally CodeSite.Send( 'MyObject. Finally'); MyObject.Free; end; |
We remember that the object won’t be created. But a static method Free will consider that the object is still available at the address 018A8B80
as the link after the first creation of the object is stored. But in reality that’s the address of another object which will be destroyed. To make sure that it is so, it is enough just to press Button2.
1 2 3 4 |
procedure TfMain.Button2Click(Sender: TObject); begin CodeSite.Send( 'MyObject2.ClassName', MyObject2.ClassName ); end; |
We will get a well-known Access Violation error.
As a result, due to using this construction for creating and destroying an object, we have destroyed another object. Similar errors are quite dangerous when it comes to lists of objects. And the search for such errors is a rather challenging process. It is obvious that nothing of this kind happens if we create MyObject for the second time in a way that is applied in the majority of cases.
1 2 3 4 5 6 7 8 |
MyObject:= TMyClass.Create(True); try CodeSite.Send('Try creating MyObject'); finally CodeSite.Send( 'MyObject. Finally'); MyObject.Free; end; |
Where can I read more on access violations, Delphi objects, and classes?
For a deeper understanding of the information, I recommend reading the following article http://rvelthuis.de/articles/articles-pointers.html, as well as the materials provided by Aleksandr Alekseev https://www.gunsmoker.ru/2009/04/freeandnil-free.html
This article was written by an Embarcadero Tech Partner. For more articles by Softacom and our other Tech Partners click the following link: https://blogs.embarcadero.com/category/tech-partner/
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition
Hi,
Thanks a lot for this article, I’ve read it with interest and it made me think (and somehow rethink) my coding habits.
BUT, eventually, I cannot agree with you.
First, to prove your point, you’ve followed a lot of bad practices:
1 – You make use of global variables
2 – You reuse your (global) variables
3 – You don’t use FreeAndNil for reuse your variables (it’s there for that)
Secondarily I think the order you propose is certainly correct and has no drawbacks when you have one object, but problems arise with multiple objects and maybe such of an informative article like this should mention that.
Suppose you have 2 objects to be created:
var
MyObject: TMyClass;
MyObject2: TMyClass;
begin
MyObject := TMyClass.Create();
MyObject2 := TMyClass.Create(True);
try
//
finally
MyObject.Free;
MyObject2.Free;
end;
end;
In this example you’ll leak MyObject.
So the solution should be:
var
MyObject: TMyClass;
MyObject2: TMyClass;
begin
MyObject := TMyClass.Create();
try
MyObject2 := TMyClass.Create(True);
try
//
finally
MyObject2.Free;
end;
finally
MyObject.Free;
end;
end;
Do it with more than two objects and welcome to indentation hell.
Another solution is instead to do this:
var
MyObject: TMyClass;
MyObject2: TMyClass;
begin
MyObject := nil;
MyObject2 := nil;
try
MyObject := TMyClass.Create();
MyObject2 := TMyClass.Create(True);
finally
MyObject.Free;
MyObject2.Free;
end;
end;
This same “trick” is used in VCL too and will ensure that nothing will leak (you have to pay attention to free the objects in the same order you have created them).
A variant on the same theme is to create only the first object before the try..finally block and the others inside it (I personally don’t like it but I’ve seen it in VCL source).
Moreover this will let you create objects only when (and if) you need it without a memory leak, for example:
var
MyObject: TMyClass;
MyObject2: TMyClass;
begin
MyObject := nil;
MyObject2 := nil;
try
MyObject := TMyClass.Create();
If random(10) = 4
then MyObject2 := TMyClass.Create();
finally
MyObject.Free;
MyObject2.Free;
end;
end;
Another solution (when you have many objects) is to create a single TObjectList before the try..finally block, add all your created objects to the list inside the try block and free only the list in the finally block.
Of course one may state that if you have a lot of objects to be created you should redesign your code flow, but sometimes you have no other practical ways to get things done.
Please correct me if I’m wrong in any way.
Thank you,
Mauro.
I think the point here is that Softacom are showing examples of bad practices (as you mention) which DO happen in code, quite regularly. Your last example shows a specious pattern of object creation which would result in a memory leak – and an obvious one, surely, which is the kind of thing which does indeed strengthen Softacom’s point: creating objects can be an area people often get wrong. The fact you don’t agree the best way to go about getting it wrong illustrates that there are plenty of ways to get it wrong! 😂
Personally, I use
IInterface
a *LOT* nowadays. The more code you write the more you realize that coding to an interface rather than a concrete object has lots of advantages, one of which is having the burden of unintentionally creating memory leaks taken off your shoulders. Sure, they can cause their own problems in ‘new and exciting ways’ but, overall, they also make life a lot easier too.Thanks for your thoughtful insights. 🙂👍
Hi Ian,
thanks a lot for your replay.
Surely my bad, but I cannot understand the “obvious” memory leak you’re talking about (indentation was removed in post so maybe I cannot see something really evident).
Thans a lot again,
Mauro.