Module 3: Dynamic Memory

You own the heap. No GC, no safety net.

Lessons

IDTitleDuration
3-1malloc, calloc, realloc, free30 min
3-2The Bugs: Leaks, Double-Free, Overflow30 min
3-3valgrind and AddressSanitizer25 min
3-4How malloc Works Inside25 min
3-5Arena Allocators30 min

Project

| 3-proj | Project: vec_t — Dynamic Array | 60 min |

Key Concepts

malloc, memory-safety, valgrind, arena-allocators

PDF Status

  • PDF ch04 (Dynamic Memory) — Complete (written 2026-04-08).
    • Key Concepts: allocation function semantics table, four bug classes table, detection tools table, arena overview.
    • Going Deeper: heap internals (chunk metadata, free lists, sbrk vs mmap, fragmentation, calloc optimization), bug taxonomy (ASan shadow memory, Valgrind GC scan), ownership conventions (transfer/borrow/arena, cleanup-via-goto), realloc invalidation problem.
    • Real-World Connection: Redis src/zmalloc.c — PREFIX_SIZE trick for size tracking, custom OOM handler.
    • Exercises 4.1–4.4 + solutions + test files.

Cross-Module Dependencies

  • Requires m02-pointers-memory-model: pointer arithmetic and void * are used extensively throughout this module. The “what is a dangling pointer” question requires solid understanding of the address-space model from ch03.
  • Feeds into m04-structs-unions-bitfields: struct allocation with malloc(sizeof(*p)) pattern, padding and alignment considerations for packed structs on the heap.
  • Feeds into m09-concurrency: malloc is not thread-safe without a lock; arena allocators eliminate contention for per-thread allocation. The used_memory pattern from Redis zmalloc is a simpler version of what thread-safe allocators (jemalloc, tcmalloc) do per-thread.

Pedagogical Notes

What this module is actually teaching

The module teaches ownership discipline. The four allocation functions are easy to memorize; the hard part is internalizing that every heap allocation creates an obligation — someone must call free, exactly once, at the right time.

Students coming from GC languages have never had to think about this. The bugs in lesson 3-2 (lost pointer, early return, realloc failure) are not obscure edge cases — they are the canonical forms of every real heap bug. Drill these patterns until they’re reflexes.

Common traps by lesson

3-1 (allocation functions)

  • realloc NULL trap: p = realloc(p, n) leaks p if realloc fails. This is the most common leak in beginner C code that uses dynamic arrays.
  • calloc vs malloc confusion: students write calloc(1, n) thinking “one allocation of n bytes” — correct, but the intended idiom is calloc(count, elem_size) to make the element count explicit and avoid overflow in count * elem_size.
  • sizeof(*ptr) vs sizeof(type): students use sizeof(int) everywhere, then change the type and forget to update the allocation. Teaching sizeof(*ptr) early prevents this.

3-2 (bugs)

  • Students often don’t believe that use-after-free “works” without a crash. Seeing printf("%d\n", *freed_ptr) print 42 (stale data still there) before it fails later is the lesson. ASan makes this concrete.
  • The security connection (UAF in Chrome/Linux CVEs) motivates why this matters beyond toy programs.

3-3 (Valgrind + ASan)

  • Reading the three-section structure of an ASan report (what happened → where accessed → where freed → where allocated) is a skill. First-time students read only the top line and miss the allocation/free sites that tell them why it happened.
  • Valgrind’s “definitely lost” vs “still reachable” distinction matters: still reachable at exit is often benign (process-lifetime singletons); definitely lost is always a bug.

3-4 (malloc internals)

  • The chunk metadata diagram (hidden header before the pointer) is the key mental model. Once students see this, heap overflow ceases to be mysterious: it overwrites the next chunk’s header.
  • Fragmentation demo: students assume free memory is contiguous. The alternating-sizes example breaks this assumption visually.

3-5 (arena allocators)

  • The align_up bitmask (n + align - 1) & ~(align - 1) confuses students who haven’t seen bitwise alignment before. Walk through it with align = 8: any n from 0–7 rounds to 0, any n from 8–15 rounds to 8, etc.
  • The key insight to emphasize: arena allocators don’t remove the need for free — they batch it. You still release memory, just all at once.

Key code snippets

The realloc safe pattern — worth drilling until automatic:

// WRONG (leaks on failure):
p = realloc(p, new_size);
 
// CORRECT:
int *tmp = realloc(p, new_size);
if (!tmp) { free(p); return -1; }
p = tmp;

Arena bump allocation — the whole pattern in 10 lines:

static size_t align_up(size_t n, size_t align) {
    return (n + align - 1) & ~(align - 1);
}
 
void *arena_alloc(Arena *a, size_t n, size_t align) {
    size_t pos = align_up(a->pos, align);
    if (pos + n > a->cap) return NULL;
    void *ptr = a->buf + pos;
    a->pos = pos + n;
    return ptr;
}

Cleanup-via-goto — the C idiom for multi-step allocation:

int init(Ctx *ctx) {
    ctx->buf = malloc(BUF_SIZE);
    if (!ctx->buf) goto fail_buf;
    ctx->idx = calloc(N, sizeof(Entry));
    if (!ctx->idx) goto fail_idx;
    return 0;
fail_idx: free(ctx->buf);
fail_buf: return -1;
}

Notes

Deepened 2026-04-08 after completing PDF ch04.