This simulator grows a call stack and a heap side by side. Each nested call pushes a frame and allocates a heap block; returning pops the frame, but only an explicit free releases the block. You can leave out the frees or keep a pointer and use it after cleanup.
• A 3D scene with call stack frames, a heap allocation arena, a saved-pointer path and a free-list and diagnostic station. • A nested calls and allocations slider (1 to 6), an Omit explicit frees checkbox and an Attempt saved-pointer access after cleanup checkbox. • Readouts for live stack frames, allocated heap blocks, allocation calls, free calls, unreachable allocated blocks at the end and whether an invalid freed-object access was detected. • Restart demonstration and Advance event buttons, plus three experiments.
Stack frames follow call structure: live frames equal calls minus returns, and they vanish automatically on return. Heap blocks follow explicit ownership: allocated blocks equal allocations minus frees, and returning from a function does not free what it allocated. With clean unwinding both counts end at zero. With frees omitted, the stack is empty but the allocated blocks remain with no path to them — leaked. With the stale-pointer option, a saved pointer is used after its block is freed, and the lab flags the access as invalid.
This is a manual allocation model inspired by C and C++, with no garbage collector, reference counting or allocator fragmentation. Frames and blocks have equal illustrative sizes. A stale-pointer access is diagnosed by the lab; real undefined behavior has no deterministic result, so a program that appears to work is not evidence of correctness. Each event lasts 0.8 animation seconds.
No. Manual heap lifetimes need explicit ownership and cleanup. The leak experiment shows an empty stack with four allocated blocks still outstanding.
No. The numeric address is still there, but the allocation is no longer alive. The use-after-free experiment flags the access as invalid.
Blocks that were allocated and never freed, and to which no live frame still holds a pointer. They are the leak count at the end of the run.
Their lifetime is tied to the call: a frame exists only while its function is active, so live frames always equal calls minus returns.