Skip to content

nostdahl.com

A technology journal
Article archive
← All articles
Programming

Who Owns This Object? Memory Management in Delphi and Free Pascal

Bjørn T. Nøstdahl

~6 min read

When I tell C# developers that Delphi and Free Pascal have no garbage collector, the reaction is usually a wince. Then I show them the code and the wince goes away. Object Pascal has a small set of ownership rules, and once they are habits, memory management becomes something you notice about as often as you notice semicolons.

This article is the one I promised in the language refresher. It is written for people arriving from a garbage-collected world.

One model everywhere

A bit of history first. For a few years Delphi used automatic reference counting for all objects on mobile platforms while keeping manual management on desktop. That split caused more confusion than it solved, and it was removed in Delphi 10.4 in 2020. Today Delphi and Free Pascal share the same model on every platform:

  • Classes are freed explicitly, or by something that owns them.
  • Interfaces are reference counted.
  • Strings, dynamic arrays and variants are managed by the compiler.
  • Records live wherever they are declared and need nothing from you.

That is the whole list. What follows is how to apply it.

Rule 1: Every Create has a Free, and the Free is in a finally block

The fundamental pattern. If a routine creates an object and uses it locally, free it in a try..finally block so that an exception in the middle cannot leak it.

pascal
procedure ExportReadings(const FileName: string);
var
  Lines: TStringList;
begin
  Lines := TStringList.Create;
  try
    Lines.Add('sensor;celsius;taken_at');
    // ... fill the list ...
    Lines.SaveToFile(FileName);
  finally
    Lines.Free;
  end;
end;

Note that Create happens before try. If the constructor itself fails there is nothing to free, and the exception propagates on its own. Put the Create inside the try block and you will one day call Free on an uninitialised variable.

When two objects are needed, nest the blocks, or create both before a single try with the variables initialised to nil first. Free on a nil reference is safe, which is what makes the second form work.

Rule 2: Components are freed by their owner

Every class descending from TComponent takes an owner in its constructor. When the owner is destroyed, it destroys everything it owns. This is why you never write a destructor for a form that frees its buttons: the form owns them.

pascal
Timer := TTimer.Create(Self);  // Self is the form; the form will free it
Timer.Interval := 1000;
Timer.OnTimer := @HandleTick;  // in Delphi mode, drop the @

Pass nil as the owner when you want to manage the lifetime yourself. I do that for objects created in a routine and freed in the same routine, and for objects whose lifetime does not match any form. Ask yourself for every TComponent you create: who should free this, and does that owner outlive every use?

Rule 3: Collections can own their contents

TObjectList<T> and the classic TObjectList take an OwnsObjects flag. When it is true, removing an item or freeing the list frees the objects inside it. This turns a list of objects into a single thing to free.

pascal
FSensors := TObjectList<TSensor>.Create(True);  // True = OwnsObjects
FSensors.Add(TSensor.Create('boiler'));
FSensors.Add(TSensor.Create('attic'));
// ...
FSensors.Free;  // frees both sensors and the list

The flag has a sharp edge. If something else also holds a reference to an item in an owning list, that reference becomes dangling when the list is freed. A list should own its contents or share them, never both.

Rule 4: Interfaces are reference counted, so use them for shared lifetimes

Any object whose lifetime is shared between several parts of the program is a candidate for an interface. The runtime counts references, and the object frees itself when the last one goes away. TInterfacedObject gives you that counting for free.

pascal
type
  ISettings = interface
    ['{2B6A1F38-3D50-4F5E-8E6B-9B2B1C3E4D5F}']
    function GetValue(const Key: string): string;
  end;

var
  Settings: ISettings;
begin
  Settings := TIniSettings.Create('app.ini');
  Settings.GetValue('theme');
end;  // released automatically

Two warnings that have cost me hours each:

  • Never mix the two kinds of reference to the same object. If you hold an object variable and an interface variable to the same TInterfacedObject, the interface will free it while the object variable still points at it. Pick one.
  • Circular references leak. If A holds an interface to B and B holds one to A, the count never reaches zero. Delphi has the [weak] attribute to break cycles; Free Pascal does not, so there you break the cycle by design, usually by giving one side a plain object reference.

Rule 5: Destructors must survive a half-built object

If a constructor raises an exception, the destructor runs anyway. That means every destructor must be safe to call on an object whose fields may still be nil. Since Free checks for nil, this is mostly automatic, but it is why you should never put anything other than Free calls and simple cleanup in a destructor.

pascal
destructor TSensor.Destroy;
begin
  FReadings.Free;   // safe even if the constructor failed before creating it
  FLogger := nil;   // interface reference, released by assignment
  inherited;
end;

Always mark the destructor override and call inherited. If you forget override, Free calls the TObject destructor instead of yours and your fields leak silently.

Free versus FreeAndNil

Free destroys the object but leaves the variable pointing at dead memory. FreeAndNil(Variable) destroys it and sets the variable to nil. For local variables that go out of scope immediately, Free is enough. For fields that may be checked later, or that may be freed more than once in different code paths, FreeAndNil turns a crash in some faraway place into an Assigned check that behaves. I use FreeAndNil for fields and Free for locals, and I have stopped arguing about it.

Tools that find what you missed

Nobody gets this perfect, so let the tools check your work.

Delphi ships FastMM as its memory manager. Set ReportMemoryLeaksOnShutdown := True in the project file and every leaked object is reported in a dialog when the program exits. For serious investigations, the full FastMM debug mode records the stack trace of each allocation.

Free Pascal has the heaptrc unit. Compile with -gh (in Lazarus: Project Options, Debugging, "Use Heaptrc unit") and the program prints a leak report on exit, including the source line of each unfreed allocation. It slows the program down, so leave it for debug builds.

Run your test suite with leak detection on, in CI, and treat a leak as a failed test. It is the cheapest security control you will ever add: a program that frees memory predictably is also a program that is much harder to crash from the outside.

Does it get tiring?

Honestly, no. After a few weeks the patterns are automatic, and in exchange you get deterministic cleanup: files close when you expect, sockets release when you expect, and there is never a pause while a collector runs. For the kind of tools I build, that predictability is worth the few extra lines.

Questions for you

  • What is the memory bug that cost you the most time in Delphi or Free Pascal, and what habit did you adopt afterwards?
  • Do you use interfaces for everything shared, or do you still pass object references around and document the owner?
  • Is leak detection part of your CI pipeline? If not, what stops you?

Email me at bjorn.nostdahl@nostdahl.com. The next article moves from memory to storage: SQLite from Delphi and Lazarus, side by side.

Continue exploring
← All articlesMore in Programming →