Programming Fundamentals Using C++

L27 · Dynamic Memory: `new`, `delete`, Leaks, Dangling Pointers, `std::vector` as Managed Memory

Module 14 — Dynamic Memory and Structures · Week 14 · Lecture 27 of 32 · 120 minutes Outcomes: CLO-7 · PF-14.1, PF-14.2 · LEARNING_OUTCOMES.md

Learning objectives

  1. Allocate and release heap memory with new/delete (scalar) and new[]/delete[] (arrays), matching forms exactly and explaining why mismatching forms is undefined behavior (PF-14.1).
  2. Identify memory leaks, dangling pointers, and double-deletes in code, and repair each with the correct fix (PF-14.1).
  3. Explain RAII and why std::vector/std::string are the course default, converting a leak-prone raw-memory program to a leak-free vector version (PF-14.2).

Prerequisites

L25 (pointers, addresses, state diagrams); L17 (array memory model); L03 (lifetime — stack objects die at scope end).

Concept sequence

  1. The stack vs heap: two memory regions with different rules
  2. new/delete: explicit heap lifetime
  3. new[]/delete[] for dynamic arrays; runtime-sized allocation
  4. The three failure modes: leak, dangling, double-delete
  5. RAII: tying lifetime to scope (vector as the safe owner)
  6. Conversion lab: raw → vector

Teaching topics (detailed)

C++ examples required

FileRole
dynamic_memory_caution.cpp ✅three labeled failure modes (each reproducible-safe: leak via counter, dangling via re-set check, double-delete commented) + the corrected versions
(live) raw_to_vector.cppside-by-side raw vs vector implementations of the same stats task

Common student misconceptions

Conceptual explanation (beginner-first)

Until now, every variable's size was fixed in the source code — int scores[30]; is 30 forever. But programs often need memory whose size they only know at run time ("allocate room for n students"). The heap (or free store) is memory you request explicitly with new and release explicitly with delete — and that pair of responsibilities is the whole lesson: every new must be matched by exactly one delete (or delete[] for arrays), and failing to do so leaks memory silently.

Three failure modes dominate, and all three are silent or delayed: the leak (allocated, never freed — the program slowly eats RAM), the dangling pointer (freed, but the pointer still points there — using it is undefined behavior), and double delete (freeing twice — corrupts the allocator). The disciplines that prevent them: null-check before use, null-out after delete, never dereference a freed pointer, and match allocation forms exactly.

Then the professional punchline: modern C++ makes new[]/delete[] rare. std::vector<int> (a library array that grows and manages its own heap memory) handles allocation and release automatically — when the vector goes out of scope, its memory is freed. That pattern — resource release tied to object lifetime — is called RAII, and it is why the course default is vector, with raw new taught so the mechanism is understood, not feared.

Terminology and definitions

TermDefinition
StackAutomatic storage: locals and parameters; freed at scope exit
Heap / free storeExplicit memory: requested with new, released with delete
new / deleteAllocate one object / release it
new[] / delete[]Allocate an array / release it — forms must match
Memory leakAllocated but never freed; invisible, cumulative
Dangling pointerPoints to freed memory; any use is undefined behavior
Double deleteFreeing the same block twice — allocator corruption
nullptr resetp = nullptr; after delete; deleting null is a safe no-op
std::vector<T>Library dynamic array; manages its own heap memory (RAII)
RAIIResource acquisition tied to object lifetime — release happens automatically
Runtime sizingint* data = new int[n]; where n is known only at run time

Syntax and C++ examples

// runtime-sized single value and array
int n{};
std::cin >> n;
int* data{new int[n]{}};          // n zeros; size fixed AT RUN TIME

for (int i{0}; i < n; ++i)
    data[i] = i * i;              // same indexing as a normal array

long long total{0};
for (int i{0}; i < n; ++i)
    total += data[i];

delete[] data;                    // match the [] form — exactly once
std::cout << total << '\n';

// single object
int* p{new int{42}};
std::cout << *p << '\n';
delete p;
p = nullptr;                      // null-out: dangling becomes testable

// the modern default: std::vector does all of this management itself
#include <vector>
std::vector<int> v(static_cast<std::size_t>(n));
for (std::size_t i{0}; i < v.size(); ++i)
    v[i] = static_cast<int>(i) * static_cast<int>(i);
// no delete — freed automatically when v leaves scope (RAII)

Line-by-line code explanation

examples/dynamic_memory_caution.cpp (three labeled failure modes, each followed by its corrected version):

  1. Leak mode: a loop allocates a fresh block each iteration and drops the only pointer into it — the old block becomes unreachable but is never freed. The corrected version frees (or reuses) before reallocating. Teaching point: the leak is silent; only a memory monitor would see it.
  2. Dangling mode: a pointer is deleted, then read — the program may print the old value, zeros, or crash: undefined behavior. The fix nulls the pointer after delete, making "is it valid?" answerable with if (p != nullptr).
  3. Double-delete mode: delete p; twice — allocator corruption (presented as a commented demo; running it is not safe). The safe idiom: null-out after delete, because delete nullptr is defined as a no-op.
  4. The corrected file finishes with the same task done by std::vector<int> — same results, zero management code: the RAII contrast is the closer.

Output prediction questions (with answers)

  1. new int[10] must be freed by — ? — delete[] (form must match).
  2. Memory usage climbs every iteration — which failure mode? — Leak; suspect every allocation line not paired with a delete.
  3. delete p; p = nullptr; delete p; — ? — Safe: deleting null is a no-op.
  4. Reading through a freed pointer — ? — Undefined behavior: possibly old value, possibly garbage, possibly a crash.
  5. Why does the vector version have no delete? — RAII: the vector's destructor frees its heap block at scope exit.

Common errors and debugging examples

ErrorSymptomFix
delete on new[] memoryUB — wrong pairingdelete[] matches new[]
Leak in a loopSlow RAM growth, no crashFree before reallocating; or vector
Use after deleteRandom garbage / crashNull-out after delete; check before use
Double deleteAllocator corruptionDelete exactly once; null-out pattern
Losing the pointer (overwriting it)Orphaned block — leakKeep the pointer until deleted
Raw new[] where a vector fitsAll of the above risksDefault to std::vector (course rule)

Classroom demonstrations

  1. The leak that "works": run the leaky loop — nothing crashes; show Task Manager/top climbing. Silence is the danger.
  2. Dangling roulette: print through a freed pointer a few times — differing outputs across runs demonstrate UB honestly.
  3. RAII relief: run the vector version of the same task — then delete the vector's cleanup line conceptually: there is none. That is the lesson.

Guided student activities

Failure-mode clinic (20 min): six short programs on cards (one leak, one dangling read, one double-delete, one mismatched delete form, one missing null-out, one correct); teams diagnose severity (crash now / crash later / silent), fix, and rank by real-world danger; debrief ranks silent leaks highest.

Practice problems

Summary

The heap buys runtime sizing at the price of manual pairing: every new[] needs exactly one delete[], and the three silent failure modes (leak, dangling, double delete) have three disciplines (match forms, null-out, never reuse freed memory). std::vector wraps the whole contract into RAII — which is why the course default is vector, and raw new is taught for understanding. Next (L28): structures — user-defined types that bundle data.

Exit ticket / formative assessment

  1. Which delete frees new int[10]?
  2. A program's memory usage climbs every loop iteration. Which failure mode, and which line type is the suspect?
  3. Why does the course default to std::vector over new[]? (One sentence, one keyword: RAII.)

Estimated time allocation (120 min)

SegmentMinutes
Recall (passing quiz) + stack vs heap10
new/delete + dynamic arrays + disciplines40
Break10
Failure modes + RAII + vector conversion35
Failure-mode clinic20
Exit ticket + L28 preview5
Programming Fundamentals Using C++ · C++17 · 16 weeksBack to top ↑