Module 3: Dynamic Memory
You own the heap. No GC, no safety net.
Lessons
| ID | Title | Duration |
|---|---|---|
| 3-1 | malloc, calloc, realloc, free | 30 min |
| 3-2 | The Bugs: Leaks, Double-Free, Overflow | 30 min |
| 3-3 | valgrind and AddressSanitizer | 25 min |
| 3-4 | How malloc Works Inside | 25 min |
| 3-5 | Arena Allocators | 30 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:
mallocis not thread-safe without a lock; arena allocators eliminate contention for per-thread allocation. Theused_memorypattern 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)
reallocNULL trap:p = realloc(p, n)leakspif realloc fails. This is the most common leak in beginner C code that uses dynamic arrays.callocvsmallocconfusion: students writecalloc(1, n)thinking “one allocation of n bytes” — correct, but the intended idiom iscalloc(count, elem_size)to make the element count explicit and avoid overflow incount * elem_size.sizeof(*ptr)vssizeof(type): students usesizeof(int)everywhere, then change the type and forget to update the allocation. Teachingsizeof(*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 reachableat exit is often benign (process-lifetime singletons);definitely lostis 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_upbitmask(n + align - 1) & ~(align - 1)confuses students who haven’t seen bitwise alignment before. Walk through it withalign = 8: anynfrom 0–7 rounds to 0, anynfrom 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.