Memory Bugs and How to Find Them
Manual memory management brings a family of bugs that other languages prevent: writing past the end of a buffer, using memory after freeing it, freeing twice, and leaking. They can corrupt data silently and are behind many security vulnerabilities. Learn to recognise them — and let tools find them for you.
The usual suspects
| Bug | What happens |
|---|---|
| Buffer overflow | Reading or writing outside an array or allocated block |
| Use after free | Using memory through a pointer after it was freed |
| Double free | Freeing the same block twice |
| Memory leak | Never freeing memory you no longer need |
| Uninitialised read | Reading a variable or malloced memory before writing it |
| Dangling stack pointer | Using the address of a local variable after its function returned |
| Null dereference | Using a pointer that is NULL |
All of them are undefined behaviour. The frustrating part is that the program often seems to work — until different input, a different compiler or an optimisation level makes it crash or produce wrong results.
AddressSanitizer
GCC and Clang include AddressSanitizer (ASan), which adds checks around every memory access while you test. Compile with:
gcc -g -fsanitize=address -fno-omit-frame-pointer main.c -o main ./main
When something goes wrong, ASan stops the program and prints a detailed report: the kind of error, the exact line, and where the memory was allocated and freed. The full report includes memory addresses that differ every run; the examples below show the program's output and the report's final SUMMARY line.
An off-by-one overflow
#include <stdio.h> #include <stdlib.h> int main(void) { int *marks = malloc(3 * sizeof *marks); if (marks == NULL) return 1; for (int i = 0; i <= 3; i++) { // <= should be < marks[i] = 50 + i; } printf("%d\n", marks[0]); free(marks); return 0; }
SUMMARY: AddressSanitizer: heap-buffer-overflow main.c:8 in main
Without the sanitizer this program usually runs and prints 50 — the bug would go unnoticed. ASan catches the write to marks[3] on line 8.
Use after free
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char *name = malloc(16); if (name == NULL) return 1; strcpy(name, "temporary"); free(name); printf("%s\n", name); // the memory no longer belongs to us return 0; }
SUMMARY: AddressSanitizer: heap-use-after-free main.c:10 in main
Setting a pointer to NULL right after freeing it turns this kind of bug into an immediate, obvious crash instead of silent corruption.
Double free
#include <stdlib.h> int main(void) { int *data = malloc(10 * sizeof *data); free(data); free(data); return 0; }
SUMMARY: AddressSanitizer: double-free main.c:6 in main
Stack buffer overflow
#include <stdio.h> #include <string.h> int main(void) { char code[8]; const char *input = "ABCD-1234-XYZ"; strcpy(code, input); // 13 characters + '\0' into 8 bytes printf("%s\n", code); return 0; }
SUMMARY: AddressSanitizer: stack-buffer-overflow main.c:7 in main
This is the classic security hole: input longer than the buffer overwrites neighbouring memory. The fix is to check lengths or use a size-limited function such as snprintf.
Finding leaks
On Linux, AddressSanitizer also reports leaks when the program exits (LeakSanitizer). On macOS, use the leaks command (leaks --atExit -- ./main). Valgrind (valgrind --leak-check=full ./main) is another popular Linux tool that finds leaks and uninitialised reads without recompiling.
Other sanitizers
| Option | Finds |
|---|---|
-fsanitize=address | Overflows, use after free, double free (and leaks on Linux) |
-fsanitize=undefined | Signed overflow, bad shifts, misaligned access and other undefined behaviour (lesson 54) |
-fsanitize=memory (Clang, Linux) | Reads of uninitialised memory |
-fsanitize=thread | Data races in multithreaded code |
Sanitized programs run slower and use more memory, so use them for development and testing, not release builds. Running your test suite under ASan and UBSan regularly catches most memory bugs long before users do.
Habits that prevent memory bugs
- Compile with
-Wall -Wextraand fix every warning. - Initialise every variable and pointer; set pointers to
NULLafterfree. - Keep each array's length next to it, and loop with
i < length. - Use size-limited functions (
snprintf,fgets) and check lengths before copying. - Give every allocation one clear owner who frees it, and free in the reverse order of allocation.
- Test with sanitizers on.
Try it yourself
This function has a subtle bug: it allocates one byte too few for the copy. Spot it, then compile the program with -fsanitize=address to confirm, and fix it.
MEMORY MATTERS