Memory Management in Delphi

Dec 14, 2024    m. Mar 23, 2025    #programming  

Object Pascal, or better known as Delphi is a great programming language with built-in memory safety measures that make it easy to avoid memory-based vulnerabilities. Despite that, there are still mistakes that some make when handling memory in Delphi.

Tackling Memory Management

Let’s look at this snippet:

procedure WriteStringList(s:TStringList);
begin
	WriteLn(s.Text);
end;

procedure Main();
var
    words : TStringList;
begin
    words.Create();
    words.Add('This word');
    words.Add('This other word');
    words.Add('Some other other word');
    WriteStringList(words);
end;

To some Pascal developers, any issues may seem obvious, but to others they may be oblivious of the issues at bay.

Creating objects

One common misconception is that var.Create() is a valid constructor call, this often leads to confusing errors. When creating objects in Pascal, one must make sure to reference the type of the object they are creating. It seems obvious, since other languages such as C++ and Java do not let you compile a program using an invalid constructor call, the fact that Pascal lets you compile this, makes it trivial to make this mistake.

The issue is that doing this creates a nil object, which as one can tell, has no data. I like to think of it as creating something from nothing, as the object has no data, calling the constructor for nil would result in well, nil. As opposed to creating something from a template which has the layout for how the object.

The correct syntax would rather be

procedure Main();
var
    words : TStringList;
begin
-   words.Create();
+   words := TStringList.Create();
    words.Add('This word');
    words.Add('This other word');
    words.Add('Some other other word');
    WriteStringList(words);
end;

This is a mistake that is easy to miss, for not only novice Pascal developers, but experienced Pascal programmers as well. Furthermore, it can be a difficult mistake to debug.

Freeing Objects

Another error in my example is not freeing my objects. One thing we must also remember to do is to free every object that we create. When you call the constructor of an object its destructor must follow, I like using a creation is followed by destruction motto to remember this.

procedure WriteStringList(s:TStringList);
begin
    WriteLn(s.Text);
end;

procedure Main();
var
    words : TStringList;
begin
    words := TStringList.Create();
    try
        words.Add('This word');
        words.Add('This other word');
        words.Add('Some other other word');
        WriteStringList(words);
    finally
    	words.Free();
    end;
end;

This can be easy to miss. This is mainly because when using Delphi, you are technically allowed to create an object without freeing it, 9 times out of 10 times your program will not crash, in my own experience. Avoiding this pattern is important to avoid crashes that come from un-freed objects, because an access voilation error can occur and those can be difficult to debug.

Objects passed between forms

When you create an object, it is allocated to the heap in memory, and it is easy to assume that anything that is allocated will be freed from memory automatically, much like the expectation in previous example. This is a mistake I made a lot when passing data between forms.

Take this as an example

Form 1:

procedure Tform1.ToForm2();
var
    User : TUser;
    Form2 : TForm2;
begin
    // Assigning our name and age
    User := TUser.Create('Daru',12);

    // Not form2.create()!
    form2 := TForm2.Create();

    // The User object is a property of form 2
    form2.User := User;

    form2.ShowModal();
end;

Form 2:

procedure TForm2.DoStuff();
begin
    WriteLn('User is ' + User.name);
    WriteLn('Age is ' User.age);
    User.Age := 30;
    DoClose();
end;

procedure TForm2.DoClose();
begin
    self.ModalResult := mrClose;
end;

Here, the toForm2 function creates the User object, calling the constructor, which assigns the user-name and their age. The second form is created and the User property of the second form is assigned to the User object in the first one, this is a common way to pass objects between forms in Delphi.

The issue here is that the user object is not freed from memory, when we pass the object, we pass a reference to the original TUser object to TForm2, don’t see the problem?

When our function, DoClose() is called, the Form2 is closed, as you can see, we assign our modal result to mrClose.

self.ModalResult := mrClose;

This creates a problem, when we set our User property to the user from Form1, we passed a reference to the object, which means that the object was supposedly freed at this point, closing a form frees the objects attached to the form, the user object is de-allocated.

When we change the user.age value to 30, we mutate the original variable, this causes a change in the reference to the object. When we close a form, the form’s destructor is called along with free, causing the form to close. The issue is, we still have the User as a property of the form, and we cannot free the form when we still hold a reference to the original object, at least, not without an access violation.

Form2 cannot destroy an object passed from Form1,since that form acts as its owner. Thus, one should free it manually.

procedure TForm2.DoStuff();
begin
    WriteLn('User is ' + User.name);
    WriteLn('Age is ' User.age);
    User.Age := 30;
    DoClose();
end;

procedure TForm2.DoClose();
begin
    User.Free; //NOT User.Destroy;
    self.ModalResult := mrClose;
end;

Of course, there are better ways to send data between forms such as shared units which store those objects and variables globally, but passing data between forms via properties is another common method. Generally, destroying an object owned by a parent form is not good practice.

Destroy vs Free

Another common error is calling the destructor procedure over the free procedure. While not being an error for say, it can lead to mistakes down the road. The destructor of an object is a virtual function that is determined at run-time based on the objects type. Free, is a method that has a known memory location, and thus we can call it after the object is freed. This is a bit different from C or C++ where a double free would cause a segmentation fault.

begin
    User.Destroy();

    // Acces violation
    User.Destroy();

    // This works
    User.Free();
    User.Free();
end

Further learning

I recommend that any Pascal developer or programmer should try other languages such as Rust or Zig. While Delphi may have decent memory management facilities and can be better than C in this right, learning from memory safe languages can be beneficial in helping one understand how other languages deal with memory and how they can improve their own methods.

Thanks for reading, I hope you gained insights into how you could improve your memory management techniques in Delphi and write safer programs.