latency.lab
Module 02

The Memory Model

Level: beginner→coreTime: ~90 minPrereq: Module 01

By the end you can

Every value your program uses lives somewhere in memory. Where it lives determines how long it lives, how fast it is to access, and who is responsible for cleaning it up. This is the module that separates people who "know C++ syntax" from people who understand C++.

The stack

Local variables live on the stack: a region of memory that grows and shrinks automatically as functions are called and return. When you declare int x = 5; inside a function, x is placed on the stack. When the function returns, x is automatically gone. No cleanup code needed.

stack.cpp
void f() {
    int a = 1;          // on the stack
    double b = 2.0;     // on the stack
}   // a and b are destroyed here, automatically, in reverse order

Stack allocation is extremely fast — it's just moving a pointer. But the stack is small (typically a few MB) and its objects can't outlive the function that created them.

The heap

The heap (also called free store) is a large region for objects whose size or lifetime you control at runtime. You request memory with new and must return it with delete.

heap.cpp
int* p = new int(42);   // allocate one int on the heap, p holds its address
std::cout << *p;            // dereference: read the value at that address
delete p;                   // return the memory. Forgetting this = leak.

Heap memory persists until you delete it — it survives across function returns. That power comes with responsibility, and that responsibility is where nearly every classic C++ bug comes from.

The three deadly sins of manual memory

sins.cpp
// SIN 1: memory leak — allocated but never freed
void leak() { int* p = new int(1); }   // p vanishes; the int is orphaned forever

// SIN 2: use-after-free — using memory you already freed
int* p = new int(1);
delete p;
std::cout << *p;          // UNDEFINED behavior — p is dangling

// SIN 3: double-free — freeing the same memory twice
int* q = new int(1);
delete q;
delete q;                 // UNDEFINED behavior — corruption or crash
The interview reality

Every one of these three is a favorite interview trap, and the Akuna debugging round is built on spotting them in unfamiliar code. You will train this explicitly in Module 11. For now, learn to feel uneasy every time you see a raw new — because someone has to delete it, exactly once, on every path including exceptions.

Why you'll rarely write new and delete

Modern C++ almost never uses raw new/delete in application code. Instead we use:

  • Stack objects whenever possible — automatic cleanup, zero bugs.
  • Containers like std::vector (Module 07) that manage heap memory for you.
  • Smart pointers like std::unique_ptr (Module 06) that delete automatically when they go out of scope.

The reason is RAII (Resource Acquisition Is Initialization): tie a resource's lifetime to an object's scope, and let the destructor free it. You'll learn to build RAII types yourself in Module 04. But the intuition starts here: manual memory management is error-prone, so we let scope and destructors do it.

A useful mental picture

A pointer is just a variable holding an address — a number naming a location in memory. *p means "the value at that address." &x means "the address of x." That's the whole concept. Module 03 makes it concrete.

Build

Exercise 02.1 — Watch the lifetime

Make object lifetime visible:

  • Write a struct with a constructor that prints "born" and a destructor that prints "died" (you'll formalize these in Module 04; for now just struct Noisy { Noisy(){...} ~Noisy(){...} };).
  • Create one on the stack inside a function and observe when "died" prints.
  • Create one with new and observe that "died" only prints when you delete it — and never prints if you forget.
  • Create two on the stack in the same scope and confirm they're destroyed in reverse order.

Takeaways

  • Stack: automatic, fast, small, tied to scope. Heap: manual, large, lifetime you control.
  • Raw new demands exactly one matching delete on every path.
  • The three sins: leak, use-after-free, double-free. All are undefined behavior (except leak, which is "just" a resource bug).
  • Prefer stack objects, containers, and smart pointers over manual memory.
  • RAII ties resource lifetime to scope. It's the reason well-written C++ rarely leaks.
Live

Loading starter code…

Checkpoint · Memory Model

5 questions · pass at 70%

Q01
Where does a local variable declared inside a function live, and when is it destroyed?
WhyLocal variables have automatic storage duration — they live on the stack and are destroyed automatically when execution leaves their scope.
Q02
What is wrong with: int* p = new int(1); delete p; std::cout << *p;
WhyAfter delete, p is dangling. Reading *p is use-after-free, which is undefined behavior. The pointer still holds the old address but the memory is no longer yours.
Q03
Why does modern C++ avoid raw new/delete in application code?
Whynew/delete are not removed and not inherently slow, but requiring a manual matching delete on every path (including exceptions) is a bug magnet. RAII automates it.
Q04
Two stack objects a then b are declared in the same scope. In what order are they destroyed?
WhyObjects are destroyed in reverse order of construction. This deterministic ordering is essential to how RAII cleans up dependent resources correctly.
Q05
What does RAII stand for and mean, in one line?
WhyRAII binds resource ownership to object lifetime. When the owning object is destroyed (at scope exit), its destructor releases the resource — automatically and even on exceptions.

Finished Module 02?

Pass the quiz to complete it automatically.

+ Note