Talking to Fable 5 [High Effort]
How do we get ‘double frees’ in Delphi code?
A “double free” in Delphi happens when the same object (or memory block) is destroyed/freed twice. The second free operates on memory that’s already been returned to the memory manager, which can corrupt heap structures, crash with EInvalidPointer/access violations, or cause subtle bugs that surface far from the actual mistake. Here are the common ways it happens in Delphi:
- Calling Free without nil-ing the reference
Obj.Free;
// ... later, another code path ...
Obj.Free; // double free - Obj still points at the dead object
Free doesn’t set the reference to nil, so a second call frees the same memory again. This is why FreeAndNil(Obj) is popular — the second call then becomes a harmless no-op because Free checks for nil.
- Two references to the same object, both “owning” it
A := TMyThing.Create;
B := A; // alias, not a copy
A.Free;
B.Free; // double free
This is especially common when objects are passed around and it’s unclear who owns them.
- Owned components freed manually
VCL/FMX components created with an owner are destroyed by that owner:
Btn := TButton.Create(Form1); // Form1 owns it
// ...
Btn.Free; // OK by itself, it de-registers from the owner
The classic bug is the reverse: keeping a reference to an owned component, letting the owner destroy it, then freeing (or using) the stale reference yourself. Similarly, TObjectList with OwnsObjects = True will free its items — if you also free them manually, that’s a double free.
- Freeing inside a destructor plus inherited cleanup
If a destructor frees a field and something else (an owned-object mechanism, a second destructor path, or an exception during Create triggering Destroy on a half-built object) frees it again. Remember: if a constructor raises an exception, Delphi automatically calls the destructor, so destructors must tolerate partially-initialized state — freeing a field that was already freed (or double-running cleanup logic) bites here.
- Interface reference counting mixed with manual Free
var
Intf : IMyInterface;
Obj : TMyObject; // TInterfacedObject descendant
begin
Obj := TMyObject.Create;
Intf := Obj; // refcount = 1
Obj.Free; // manual free
end; // Intf goes out of scope, _Release frees it again
Mixing object references and interface references to a ref-counted object is a well-known trap. Pick one lifetime model per object.
- Records/pointers with GetMem/FreeMem or Dispose
GetMem(P, Size);
FreeMem(P);
FreeMem(P); // double free - P is a dangling pointer
Same pattern as objects: nothing nils the pointer for you.
- Event handlers / notifications firing during destruction
Code in OnDestroy, Notification, or callbacks that frees objects which are already in the middle of being destroyed elsewhere.
Defenses
- Use FreeAndNil for fields and long-lived references so accidental second frees become no-ops (note it hides the logic error rather than fixing ownership, but it prevents corruption).
- Establish clear, single ownership for every object; document who frees what.
- Don’t mix manual Free with owning containers, component ownership, or interface ref-counting.
- Run with FastMM4/FastMM5 in full debug mode — it detects double frees and use-after-free by filling freed memory with a pattern and keeping freed blocks quarantined, and reports the stack trace of both the free and the re-free. This is by far the fastest way to hunt these down.
- ReportMemoryLeaksOnShutdown helps with the flip side (leaks) and often reveals confused ownership that leads to double frees.
If you’re debugging a specific crash, FastMM in debug mode is the tool to reach for first — want me to walk through setting that up?
I’m foggy on recalling the details, but people have pointed out to me ways in which Interface types can free memory out from under other parts of a program … can you comment?
Yes — this is one of Delphi’s sharpest edges. The core issue is that interface references are reference counted, and the compiler inserts _AddRef/_Release calls invisibly. When the count hits zero, the object destroys itself — regardless of whether other parts of your program still hold plain object references to it. Here are the main ways it bites:
- The fundamental trap: object reference + interface reference to the same object
var
Obj : TMyThing; // TInterfacedObject descendant
Intf : IMyInterface;
begin
Obj := TMyThing.Create; // refcount = 0 (object refs don't count)
Intf := Obj; // refcount = 1
Intf := nil; // refcount = 0 → object frees ITSELF
Obj.DoSomething; // use-after-free; Obj is dangling
end;
Nothing warned you. Intf := nil (or Intf simply going out of scope) destroyed the object while Obj still pointed at it. This is exactly “freeing memory out from under” the rest of the program.
- Hidden temporary interface references
The compiler creates invisible temporaries when you use an object in an interface context, and it releases them at the end of the statement or (depending on version/construct) the end of the routine:
procedure Consume(const I: IMyInterface);
var
Obj: TMyThing;
begin
Obj := TMyThing.Create; // refcount = 0
Consume(Obj); // implicit temp: refcount 0 → 1 → back to 0
// object may already be destroyed here!
Obj.DoSomething; // dangling
end;
Passing an object where an interface is expected creates a temporary interface reference. When that temp is released, the count round-trips through zero and the object self-destructs. The same happens with as casts to an interface type used transiently, or passing Obj as IMyInterface as an argument.
The rule of thumb people derive from this: once an object’s lifetime is managed by interfaces, never touch it through a plain object reference again — and never let the ref-count touch zero “accidentally.” Assign to an interface variable immediately at creation:
var Intf: IMyInterface;
Intf := TMyThing.Create; // refcount goes to 1 right away and stays owned
- Ref-count transiently hitting zero inside a method
A nastier variant: an object’s only interface reference gets released while one of its own methods is still executing — for example, a container removes the item mid-callback, or code inside the method reassigns the interface variable that was keeping it alive. The method continues running on a freed Self. This often “works” by luck until memory is reused.
A related classic is calling an interface method on a temporary and having the object die before you use the result you took from it (e.g., a PChar into the object’s internal string).
- Free on a ref-counted object — the double-free direction
The mirror image of #1: you free via the object reference, then the interface reference’s eventual _Release frees again, or worse, _Release decrements a ref-count field inside already-freed memory. Either way, heap corruption.
- Objects that implement interfaces but aren’t ref-counted
TComponent implements IInterface but its _Release does nothing (no self-destruction). So ref-counting silently doesn’t manage those lifetimes — which is safe from surprise-frees, but then the opposite bug appears: people assume the interface keeps the component alive, the component gets destroyed by its owner, and every interface reference to it becomes dangling without the ref-count mechanism noticing at all. Interfaces are not weak-safe pointers; a dangling interface to a destroyed component crashes just like a dangling object reference.
Any custom class can do the same by implementing _AddRef/_Release to return -1 and not free. Mixing ref-counted and non-ref-counted implementors of the same interface in one codebase is a rich source of confusion.
- Circular interface references
Two objects holding interfaces to each other never reach ref-count zero — a leak rather than a premature free — but the common fix (storing one side as a plain object pointer or unsafe “weak” reference) reintroduces the dangling-reference problem when one side dies first. Modern Delphi’s [weak] and [unsafe] attributes exist for this; [weak] references are auto-nil’ed on destruction, [unsafe] ones are not (they’re just uncounted, i.e., the footgun from #1 with a formal name).
- Exceptions and finalization order
Interface variables are finalized automatically when a routine exits (including via exception). If your cleanup code also frees the underlying object, or the finalization order releases the last reference earlier than you assumed, you get frees at moments you didn’t write any freeing code — which is precisely why these bugs feel like the runtime is pulling memory out from under you.
The practical doctrine that falls out of all this
- Pick one lifetime model per object: either manual Create/Free with object references, or interface refcounting — never both.
- If ref-counted: assign to an interface variable immediately on creation, and don’t keep or use plain object references to it afterward (or mark them [unsafe]/[weak] deliberately).
- Beware implicit temporaries: passing objects to interface-typed parameters or using as ISomething inline can create-and-release a reference you never see.
- For non-ref-counted implementors (components), remember the interface gives you access, not ownership — lifetime is still governed elsewhere.
- FastMM in full debug mode catches the resulting use-after-free/double-free with stack traces of both the destruction and the illegal access, which is usually how people finally pin down which hidden _Release did the deed.
Is this connected to a specific crash you’re chasing, or more of a design-review question? If it’s a crash, the hidden-temporary case (#2) and the mixed Free+interface case (#4) are the two I’d bet on first.