Debug Foundry

The stack forgets at the closing brace. The heap waits for an owner.

Drag to turn · pinch or ctrl-scroll to zoom · pinch to zoom

Finished state of the stack and heap simulation: a caller frame holding an owner box, an arrow from the owner to a block on the heap, the callee frame gone

Two regions
one model

Replay the ending with
int total = 41;                     // automatic: its lifetime ends at the closing brace
auto p = std::make_unique<Block>(); // owner automatic; the block is dynamic
return p;                           // ownership moves out — the block survives
int* q = &total;                    // returned, it dangles: invalid, never evaluated
p.reset();                          // destroy, then deallocate — matching the new
static int calls = 0;               // static storage duration: the whole program
Read the chapter

What it's for

  • Where every value lives, and for how long: locals that end at the closing brace, dynamic objects that survive until their owner says so. This is the diagram behind leaks, dangling pointers, and every "who frees this?" code review.
  • The ground under RAII, smart pointers and move semantics: those chapters made ownership automatic and transferable; this one shows the two storage worlds that ownership spans.

Good to know

First, the honest note the simulation opens with: C++ the language defines storage durations — automatic, static, thread and dynamic — not a physical stack or a particular OS heap. Frames and a heap are the model common toolchains implement, and a good one to think with; compilers also keep "locals" in registers, elide copies, and optimize allocations away entirely. Three clocks are worth separating: scope is where a name is visible, lifetime is when the object exists, and storage duration is how long its storage lasts — a static local has function scope but static storage duration, which is why it is not automatic and lives in no frame. An owner and its pointee run on different clocks too: p is automatic and dies with its scope, while its block is dynamic and dies when the owner destroys it — which RAII makes deterministic. The safe and unsafe returns differ exactly there: returning an owning value (or an ordinary value) moves the object out safely, while returning a pointer or reference to a local leaves it pointing at an expired object — dangling. That state is shown, never evaluated: using it is undefined behavior, not "an old value". new constructs an object in suitable storage and delete destroys it and deallocates; malloc/free move bytes only, with no constructors or destructors — and the pairs never mix (delete[] belongs with new[], not with scalar new). Finally: no universal speed claims. Deleting does not promise memory returns to the OS, dynamic storage is not unlimited, and RAII does not remove allocation failure, fragmentation, or stack exhaustion — costs and limits are implementation- and workload-dependent.

Why the steps go in this order

  • The two regions come first, with the model caveat in the caption: storage durations are the promise, frames and the heap are the common implementation.
  • Automatic before dynamic: the frame clock — created on entry, destroyed at the closing brace — is the one every reader already feels.
  • The owner and its block appear side by side: two storage durations in one statement is the chapter's whole point.
  • The safe return before the dangling one: ownership transfer is the fix, and the dangling state is shown afterward as invalid — never dereferenced, never "read back".
  • Destruction last: reset destroys the object, then deallocates — matching new with delete is the rule reviewers check.

Before you rely on it

This is a toy: plates for frames, boxes for objects, arrows for ownership, and hand-set timings — nothing here was measured. What the toy flattens: real frames have no promised addresses, sizes or growth direction; storage can outlive the object and be reused under specific rules; compilers routinely keep automatic objects off any stack; and allocation costs vary by allocator and workload, which is why the chapter makes no speed claims either way. Static and thread storage, placement new, custom allocators and arenas, coroutine frames and escape analysis are deliberately left out — each is its own on-demand chapter, not a main-path exception.

References