Memory Bugs
Every bug this chapter names has already appeared somewhere in this course — c1-6's buffer overflow, c1-7's dangling pointer, c2-2's leaks and double-frees. This chapter gathers them into one catalog, and introduces the tools real C developers actually use to catch them.
The Bug Catalog, Named Precisely
- Use-after-free — dereferencing a pointer after its memory was freed (
c1-7,c2-2) - Double-free — calling
freetwice on the same allocation (c2-2) - Memory leak — allocated memory that's never freed, with no remaining pointer to it at all (
c2-2) - Buffer overflow — reading or writing past an array or allocation's actual bounds, stack or heap (
c1-6,c1-8) - Uninitialized read — reading memory (from
malloc, or a declared-but-unset variable) before anything was written to it
A Genuine Leak Example
Each call leaks a small, fixed amount — individually harmless-looking, but accumulating without bound. Rust makes this specific pattern impossible: a value's Drop runs automatically the moment its owner goes out of scope, with no equivalent "forgot to call it" failure mode in safe code.
valgrind
A dynamic analysis tool — run an already-compiled program under it, no recompilation needed. It reports leaks, invalid reads/writes, and use-after-free, each with a stack trace showing exactly where the bad allocation, access, or free occurred.
AddressSanitizer (ASan)
A compile-time instrumentation approach — add -fsanitize=address and recompile. It catches much of the same bug class, but faster than valgrind, and crashes immediately with a detailed report at the exact moment the bad access happens, rather than only summarizing at program exit.
| Tool | Requires recompiling | Speed |
|---|---|---|
| valgrind | no | slower — full emulation |
| AddressSanitizer | yes — a compiler flag | much faster, precise timing |
Why This Chapter Exists
This is the honest cost of the tradeoff Chapter 1 named: C's speed and control come with these exact bug classes as a genuine, permanent possibility — not a temporary tooling gap that will eventually be fixed. Rust's compiler catches most of this entire category at compile time, before the program ever runs. C requires catching it at runtime, with tools like these, ideally before shipping — a real and lasting asymmetry between the two languages, not a difference in maturity or polish.
| Bug class | Rust | C |
|---|---|---|
| Use-after-free | compile-time — borrow checker | runtime — valgrind/ASan, if you run them |
| Double-free | compile-time — single ownership | runtime — valgrind/ASan, if you run them |
| Memory leak | not possible in safe, non-cyclic code | runtime — valgrind's leak checker, if you run it |
| Buffer overflow | runtime panic — always checked | runtime — ASan, if you run it; otherwise silent UB |
valgrind or an ASan-instrumented build regularly during development — not only when chasing a specific crash — catches problems long before they become a mystery in production.
Coding Challenges
Write a small program with a deliberate memory leak (malloc without a matching free), compile it, and run it under valgrind --leak-check=full. Report what the leak summary shows.
📄 View solutionWrite a small program with a deliberate use-after-free (free a pointer, then dereference it), compile it with -fsanitize=address, and run it. Report what AddressSanitizer's error output shows.
📄 View solutionExplain why "the program didn't crash" is not proof a C program is free of memory bugs, and why the same reasoning doesn't apply to a Rust program that compiles successfully.
📄 View solutionChapter 3 Quick Reference
- Bug catalog: use-after-free, double-free, memory leak, buffer overflow, uninitialized read
valgrind --leak-check=full— no recompile needed, slower, reports leaks/invalid access with stack traces-fsanitize=address— requires recompiling, much faster, crashes precisely at the moment of the bad access- Rust catches most of this category at compile time; C requires catching it at runtime, with tools
- Undefined behavior is not guaranteed to crash — a program can "work" for a long time despite a real bug
- Next chapter: the preprocessor — #define, macros, and conditional compilation