Dynamic Memory
Stack vs heap, malloc and free, and the bug taxonomy you will meet for the rest of your career
In a nutshell
Every variable you have declared so far has been on the stack: a region of memory that lives as long as the enclosing function call and is reclaimed automatically when the function returns. The stack is fast but rigid: its size is known at compile time, and you cannot keep a stack variable alive past its function’s return. The heap is the alternative. You ask the OS for a block of memory with malloc, you get a pointer, the memory stays alive until you call free. Heap memory is how C programs implement anything whose size is not known at compile time: dynamic arrays that grow, buffers sized from user input, linked data structures.
The cost is that every allocation needs a matching free (leaks otherwise), every pointer you use must still be alive (use-after-free otherwise), and every buffer size must be tracked by you (overflow otherwise). Valgrind is the tool that catches the mistakes the compiler cannot.
Why it matters
Every non-trivial program allocates dynamically. Every CTF challenge with memory corruption involves heap mechanics. Every historic memory-safety CVE — Heartbleed, Baron Samedit, the WhatsApp GIF overflow — is a heap bug. Writing Valgrind-clean code from the start is the single professional-grade habit that distinguishes someone who uses C from someone who can be trusted with C.
Key takeaways
malloc(n)returns avoid *tonbytes of uninitialized heap memory, orNULLon failure. Always check the return value.calloc(n, sz)ismalloc(n * sz)zero-initialized. Use when you need zeroed memory (and when you want the standard’s overflow check on the multiplication).realloc(p, new_n)resizes an existing allocation. It may return a different pointer; always assign back:p = realloc(p, new_n);.free(p)returns memory to the heap. Everymalloc/calloc/reallocneeds exactly one matchingfree.free(NULL)is safe and does nothing.- Stack lifetime ends at function return. Returning a pointer to a local variable from a function is a use-after-free waiting to happen.
- The heap is slower than the stack. Allocation is a system-level operation. Prefer stack allocation when the size is known and the lifetime is bounded.
- Dynamic arrays use the
{data, size, capacity}pattern.sizeis how many elements are used;capacityis how many slots are allocated; grow by doubling whensize == capacity. - Valgrind catches what the compiler cannot. Run
valgrind --leak-check=full ./progon every lab.
Bug taxonomy (the six you will meet)
- Memory leak. Forgot to
free. Small ones compound in long-running programs. - Double free. Called
freetwice on the same pointer. Can crash or (with a heap spray) give an attacker control of the allocator. - Use-after-free. Dereferenced a pointer after calling
freeon it. Classic browser CVE pattern. - Buffer overflow. Wrote past the end of a heap allocation. Adjacent heap metadata or objects corrupt.
- Uninitialized read. Read bytes from a fresh
mallocblock before writing them. Leaks stack contents (see: Heartbleed-style bugs). - Out-of-bounds index. Read or wrote
arr[i]wherei < 0ori >= capacity. Silently corrupts adjacent memory.
Lessons in this topic
| Lesson | What it covers |
|---|---|
| Stack vs Heap & Memory Layout | The four regions (text, data, bss, stack, heap) and how the OS lays them out |
| malloc, free, calloc & realloc | The four allocation primitives, the return-value check, the one-free-per-alloc rule |
| Valgrind & Dynamic Arrays | Running Valgrind, reading its output, the {data, size, capacity} growable-array pattern |
Practice and deep dives
Practice this topic: Dynamic Memory or Memory Layout drills, or browse the practice gallery.
For the OS-level picture (stack frames, what malloc asks the kernel for, how the heap grows), see the machine model deep dive. For concrete exploit walk-throughs of CWE-120, heap buffer overflow, and the Baron Samedit CVE, see the memory-safety deep dive.
What comes next
Files — another kind of handle (FILE *) that you open, read or write through, and close. The mental model is the same as heap memory: you ask for a resource, you use it, you return it.