Memory and recursion · 06 / 53
Stack, heap, pointers and references
Understand → trace → implement → justify
3 min reading · allow extra time for practiceThe call stack holds frames, return information and some local values. The heap supports dynamic objects whose lifetimes may outlast a call. “All locals are on the stack; all objects are on the heap” is not a universal language/runtime rule: optimisation, escape analysis and value types affect placement.
A C pointer stores an address; dereferencing accesses its target. Invalid, freed or out-of-bounds pointers can cause undefined behaviour. Python names bind to objects. In C#, value-type assignment copies a value, while reference-type assignment generally copies access to the same object. A struct's location depends on its containing context.
Aliasing means multiple paths reach one object. Shallow copies may share nested mutable data. Garbage collection manages unreachable objects, not timely release of every file or socket. Ownership, RAII and IDisposable establish lifetime contracts. The heap data structure is unrelated to the heap memory region.
Lifetime, ownership and addresses
In C, int *p stores an address and *p accesses its target. Returning a local variable’s address creates a dangling pointer after its lifetime ends. Aliases remaining after free can cause use-after-free. A pointer retaining the same numeric address does not prove validity. Null, dangling and out-of-bounds accesses are different failure classes.
C# reference types carry references to objects. Value-type assignment normally copies values, but a struct containing reference fields may still share objects. A growing List<T> replaces its internal array without manual free. Span<T> and ref struct lifetime rules still matter. GC timing is unsuitable for deterministic file or socket cleanup; using/IDisposable handles resources explicitly.
Python assignment binds names to objects. b=a creates no new list; a.copy() copies only the outer list, sharing nested mutable elements. Deep-copy costs and cyclic graphs need separate consideration. Deep-copying everything is not automatically good ownership design.
Foundation: Draw the aliases after shallow-copying nested lists. Intermediate: Decide which nodes an ownership graph should share. Advanced: Design immutable snapshots and explain reclamation when old versions expire, including address reuse.
A worked trace
After a=[[1],[2]]; b=a.copy(); b[0].append(9), a[0] is also [1,9]. Outer lists differ; inner lists are shared.
Cost and assumptions
Shallow copying n references is Θ(n); deep copy depends on the reachable object graph. Pointer chasing can cause cache misses.
Executable example
Run with Python 3. Assertions check a small example; they do not constitute a general correctness proof. Some advanced examples are explicitly small models or reference oracles, not full production implementations.
a = [[1], [2]]
b = a.copy()
b[0].append(9)
assert a[0] == [1, 9]
assert a is not b and a[0] is b[0]TEST YOUR UNDERSTANDING
Your turn
Does garbage collection eliminate memory leaks?
Reveal the reasoning
No. Unneeded objects retained by caches or event handlers remain reachable. Resource leaks are a separate concern.
Take it further
- Write down the input contract and the invariant before changing the example.
- Add empty, boundary, duplicate and adversarial cases where they are relevant. Compare with a simple oracle.
- Derive the cost under the stated model; measure several input sizes and explain discrepancies.
Primary reading & lecture notes
Original explanations in this course; these references provide proofs and further reading. Source links may cover the broader module.
Pat Morin · Open Data Structures ↗