Memory Management & Ownership
Delphi's mobile compilers once used ARC (automatic reference counting) for objects (XE4–10.3); Delphi 10.4 removed it, so all Delphi platforms and FPC now use the same manual model for objects.
34.1 Who frees what?
Managed automatically?
Ordinals, reals, static arrays, plain records stack / inline storage string, dynamic arrays reference counting (strings: copy-on-write) reference counting via _AddRef/_Release Records containing managed fields compiler-generated initialize/finalize Class instances you must Free them (or delegate to an owner) New/GetMem memory
Delphi's mobile compilers once used ARC (automatic reference counting) for objects (XE4–10.3); Delphi 10.4 removed it, so all Delphi platforms and FPC now use the same manual model for objects.
34.2 Ownership patterns
Local lifetime — create, try, use, finally, Free.
Owner field — a class creates objects in its constructor and frees them in its destructor.
Owning containers — TObjectList<T> (OwnsObjects = True), TObjectDictionary<K,V> with [doOwnsValues], TStringList.OwnsObjects := True.
Component ownership — TComponent.Create(AOwner): the owner frees all owned components when it is destroyed. Forms own their controls; Application owns auto-created forms.
Interfaces — lifetime by reference counting.
type
TInvoice = class
private
FLines: TObjectList<TInvoiceLine>; // owns its lines
FCustomer: TCustomer; // NOT owned: just a reference
public
constructor Create(ACustomer: TCustomer);
destructor Destroy; override;
end;
constructor TInvoice.Create(ACustomer: TCustomer);
begin
inherited Create;
FCustomer := ACustomer;
FLines := TObjectList<TInvoiceLine>.Create(True);
end;
destructor TInvoice.Destroy;
begin
FLines.Free; // frees every line too
inherited Destroy; // FCustomer is freed by whoever owns it
end;34.3 Multiple objects in one try/finally
var
A, B: TStringList;
begin
A := nil;
B := nil;
try
A := TStringList.Create;
B := TStringList.Create;
// ... use A and B ...
finally
B.Free; // Free is safe on nil
A.Free;
end;
end;34.4 Free, Destroy, and FreeAndNil
Obj.Free checks for nil, then calls the virtual destructor. Never call Destroy directly.
FreeAndNil(Obj) frees and sets the variable to nil — use it for fields that may be checked later (if Assigned(FCache) then ...).
Freeing an object does not set other references to nil. Use TComponent.FreeNotification (components) or design single ownership.
34.5 Leak detection
compile with -gh (Lazarus: *Project Options → Debugging → Use Heaptrc unit*); add -gl for line numbers; report printed at exit (SetHeapTraceOutput('heap.trc') to write to a file)
valgrind --leak-check=full./app (compile with -gv) FastMM4 (built into Delphi since 2006)
ReportMemoryLeaksOnShutdown:= True; in the project file FastMM4 full debug mode / FastMM5 detects leaks, use-after-free, buffer overruns, with stack traces Deleaker, EurekaLog, madExcept commercial leak/exception tools
program LeakCheck;
{$IFDEF FPC}{$mode objfpc}{$H+}{$ENDIF}
uses Classes;
var
SL: TStringList;
begin
{$IFNDEF FPC}
ReportMemoryLeaksOnShutdown := True; // Delphi
{$ENDIF}
SL := TStringList.Create;
SL.Add('leaked'); // oops: no Free -> reported at exit
end.34.6 A simple smart pointer using an interface
Libraries such as Spring4D (Shared<T>) and custom managed records (Delphi 10.4+, FPC 3.2 management operators) provide more complete smart pointers.
type
TAutoFree = class(TInterfacedObject)
private
FObj: TObject;
public
constructor Create(AObj: TObject);
destructor Destroy; override;
end;
constructor TAutoFree.Create(AObj: TObject);
begin
inherited Create;
FObj := AObj;
end;
destructor TAutoFree.Destroy;
begin
FObj.Free;
inherited Destroy;
end;
procedure Demo;
var
SL: TStringList;
Guard: IInterface;
begin
SL := TStringList.Create;
Guard := TAutoFree.Create(SL); // SL is freed when Guard goes out of scope
SL.Add('no try/finally needed');
end;