Memory Management

By the end of this lesson you'll know exactly where your data lives — stack or heap — how to allocate and free it by hand with new / delete , why leaks and dangling pointers happen, and how RAII and smart pointers let modern C++ manage memory for you so you almost never write delete again.

Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.

Part of the free C++ course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

Think of two ways to store your stuff. The stack is the desk in front of you: grabbing something is instant, and when you stand up everything is cleared away for you — but the desk is small. The heap is a self-storage warehouse: there's room for almost anything of any size, but you rent the unit and you must remember to return the key. Forget to return it and you keep paying for a unit you can't even reach — that's a memory leak . A smart pointer is a rental agreement that returns the key for you the moment you no longer need the unit.

1. Stack vs Heap, Automatic vs Dynamic

Every program has two regions of memory. The stack holds your ordinary local variables; this is automatic storage because each variable is created when you declare it and destroyed automatically when it goes out of scope. The heap is for dynamic storage — memory you request at runtime with new and keep until you hand it back with delete . The stack is fast but small (a few MB); the heap is huge but you manage its lifetime by hand.

Read this worked example, run it, and watch which values are cleaned up for you and which you free yourself.

2. Allocating by Hand: new and delete

new reserves heap memory and returns a pointer to it; delete hands that memory back. For a single value you pair new with delete ; for an array you pair new[] with delete[] . The forms are not interchangeable — using delete on something from new[] is undefined behaviour. The golden rule : every new has exactly one matching delete , every new[] exactly one delete[] .

Your turn. Fill in the three blanks to allocate one double , free it, and nullify the pointer.

Now do it for an array. Remember the array form of both new and delete .

3. Leaks, Dangling Pointers & Double Frees

Manual memory has three classic traps. A memory leak is new with no matching delete — the memory stays reserved but unreachable, like losing your locker key. A dangling pointer still holds an address you've already freed; reading through it is undefined behaviour (it might print garbage, might crash, might silently corrupt data). A double free is calling delete twice on the same block, which crashes. The simple discipline: set a raw pointer to nullptr right after delete — deleting nullptr is a safe no-op.

4. RAII: Let a Destructor Do the Cleanup

Remembering to delete on every path — including when an exception is thrown — is hard, and humans forget. RAII (Resource Acquisition Is Initialisation) is the C++ answer: wrap the resource in an object whose constructor acquires it and whose destructor releases it. Because C++ guarantees the destructor runs the moment the object leaves scope, cleanup becomes automatic and leak-proof. This single idea is the foundation everything else in modern C++ builds on.

5. Smart Pointers — the Modern Default

You rarely write your own RAII wrapper because the standard library already ships them: smart pointers , in memory . They look and act like raw pointers (use * and - ) but they own what they point at and free it automatically. They also encode ownership semantics — who is responsible for the memory — right in the type.

unique_ptr is the sole owner : build it with make_unique , and it frees the object at scope exit. You can't copy a unique_ptr (that would mean two owners) but you can move ownership with std::move . shared_ptr allows shared ownership through a reference count : each copy bumps the count, each destruction lowers it, and the object is freed only when the count hits zero. Default to unique_ptr ; use shared_ptr only when ownership is truly shared.

6. weak_ptr: Breaking Reference Cycles

shared_ptr has one trap. If object A holds a shared_ptr to B and B holds one back to A, each keeps the other's reference count above zero forever — neither is ever freed. That's a reference cycle , and it leaks. A weak_ptr fixes it: it observes an object without owning it, so it doesn't raise the reference count. To use what it points at, you call .lock() , which gives you a temporary shared_ptr (empty if the object is already gone). Rule of thumb: make the "back-reference" the weak one.

No blanks this time — just a brief and an outline. Create an Account on the heap using make_unique , update it through the - arrow, and notice you never write delete . Run it and check your output against the expected line.

Practice quiz

Which storage is automatic — created and destroyed for you as it goes in and out of scope?

  • The heap
  • The data segment
  • The stack
  • The code segment

Answer: The stack. Stack (automatic) storage frees itself at scope exit. Heap memory from new lives until you delete it yourself.

What is the correct way to free memory from 'new int[n]'?

Pair new[] with delete[]. Using plain delete on an array allocation is undefined behaviour.

What is a memory leak?

  • Reading freed memory
  • Deleting twice
  • Stack overflow
  • A 'new' with no matching 'delete', so the memory stays reserved but unreachable

Answer: A 'new' with no matching 'delete', so the memory stays reserved but unreachable. A leak is allocated memory that is never freed and never reclaimed — like losing your locker key. Match every new with a delete, or use a smart pointer.

What is a dangling pointer?

  • A null pointer
  • A pointer that still holds the address of memory that has already been freed
  • A pointer to the stack
  • A const pointer

Answer: A pointer that still holds the address of memory that has already been freed. A dangling pointer points at memory that was already freed (or a local that went out of scope). Reading or writing through it is undefined behaviour.

What is the simple discipline to avoid a dangling pointer after delete?

  • Set the raw pointer to nullptr right after delete
  • Call delete twice
  • Cast it to int
  • Call free() on it

Answer: Set the raw pointer to nullptr right after delete. Setting p = nullptr; after delete makes a stray later use fail loudly, and deleting nullptr is a harmless no-op.

What does RAII stand for and rely on?

  • Random Access Is Instant; pointers
  • Run And Initialise Immediately; the constructor only
  • Resource Acquisition Is Initialisation; the destructor releasing the resource at scope exit
  • Reference And Index Iterator; iterators

Answer: Resource Acquisition Is Initialisation; the destructor releasing the resource at scope exit. RAII wraps a resource so the constructor acquires it and the destructor releases it. Because the destructor always runs at scope exit, cleanup is automatic — even on exceptions.

What is the key difference between unique_ptr and shared_ptr?

  • unique_ptr can be copied freely
  • unique_ptr is the sole owner (move-only); shared_ptr co-owns via a reference count
  • shared_ptr cannot free memory
  • They are identical

Answer: unique_ptr is the sole owner (move-only); shared_ptr co-owns via a reference count. unique_ptr has one owner and can be moved but not copied. shared_ptr keeps a reference count and frees the object only when the last owner is destroyed.

Why won't 'unique_ptr<T> b = a;' compile when a is a unique_ptr?

  • a is null
  • T is abstract
  • unique_ptr has no constructor
  • Copying would mean two owners — you must move: b = std::move(a);

Answer: Copying would mean two owners — you must move: b = std::move(a);. A unique_ptr is the sole owner, so it can't be copied. Transfer ownership with std::move, after which a is empty.

Why can two shared_ptrs pointing at each other leak?

  • They corrupt the heap
  • They form a reference cycle — each keeps the other's count above zero, so neither is ever freed
  • shared_ptr is broken
  • They double-free

Answer: They form a reference cycle — each keeps the other's count above zero, so neither is ever freed. A reference cycle keeps both counts from reaching zero. Break it by making one direction a weak_ptr, which observes without bumping the count.

How do you access the object a weak_ptr observes?

  • Dereference it directly with *
  • Call .get() and delete it
  • Call .lock() to get a temporary shared_ptr (empty if the object is gone)
  • You cannot

Answer: Call .lock() to get a temporary shared_ptr (empty if the object is gone). A weak_ptr doesn't own the object, so you call .lock() to obtain a temporary shared_ptr; it is empty if the object has already been destroyed.