Memory leaks and how to find them
Understand what memory leaks are, how they accumulate in long-running programs, and how to detect and fix them with valgrind and AddressSanitizer.
- Explain what a memory leak is and how it degrades program performance
- Identify common patterns that cause leaks
- Use valgrind to detect leaks in a program
- Apply the "single owner" discipline to prevent leaks
A memory leak occurs when heap memory is allocated but never freed, and no pointer to it survives. The memory cannot be reclaimed by the allocator — the program's memory usage grows steadily until the OS kills it or it exhausts available memory. In long-running servers and daemons, even a small leak per request can accumulate to gigabytes over days.
What causes a leak
Losing the only pointer:
void leaky(void) {
int *p = malloc(100 * sizeof(int));
/* do some work... */
return; /* p goes out of scope; memory is leaked */
}When leaky returns, p is destroyed. No other variable holds the address of that memory. It can never be freed.
Early return without freeing:
int process(void) {
char *buf = malloc(1024);
if (!buf) { return -1; }
if (some_error_condition()) {
return -2; /* LEAK: buf was not freed */
}
/* ... use buf ... */
free(buf);
return 0;
}Every exit path from a function must free all allocations. The common fix: use a goto cleanup pattern (legitimate C idiom for error handling) or restructure to have a single return at the end.
int process(void) {
int result = 0;
char *buf = malloc(1024);
if (!buf) { return -1; }
if (some_error_condition()) {
result = -2;
goto cleanup;
}
/* ... use buf ... */
cleanup:
free(buf);
return result;
}Overwriting a pointer before freeing:
int *p = malloc(sizeof(int));
p = malloc(sizeof(int)); /* LEAK: first allocation is lost */
free(p); /* frees only the second */Detecting leaks with valgrind
Valgrind's memcheck tool tracks every allocation and reports any that are not freed when the program exits.
Install on Linux: sudo apt install valgrind
Compile with debug info: gcc -g -O0 leaky.c -o leaky
Run: valgrind --leak-check=full ./leaky
Output from a program that leaks 400 bytes:
LEAK SUMMARY:
definitely lost: 400 bytes in 1 blocks
indirectly lost: 0 bytes in 0 blocks
possibly lost: 0 bytes in 0 blocks
still reachable: 0 bytes in 0 blocks
suppressed: 0 bytes in 0 blocks"Definitely lost" means the allocator cannot reach the memory at all. "Still reachable" means there is a valid pointer to it at exit — not a leak per se, but a sign that cleanup code is missing.
AddressSanitizer — faster leak detection
Compile with: gcc -g -fsanitize=address,leak leaky.c -o leaky
Run normally: ./leaky
ASan prints a detailed report at program exit, including a stack trace of where the leaked memory was allocated. It is faster than valgrind (roughly 2× slower than normal execution vs valgrind's 10–50× overhead) and suitable for CI pipelines.
The "single owner" discipline
Each piece of heap memory should have exactly one owner — one variable or data structure that is responsible for freeing it. When ownership transfers (e.g., you return a malloc'd pointer from a function), document it:
/* Returns a heap-allocated string; caller is responsible for calling free(). */
char *build_greeting(const char *name) {
char *buf = malloc(strlen(name) + 8);
if (!buf) { return NULL; }
sprintf(buf, "Hello, %s!", name);
return buf;
}
int main(void) {
char *g = build_greeting("Alice");
if (g) {
printf("%s\n", g);
free(g); /* caller frees -- as documented */
}
return 0;
}Comment the ownership contract. If you are unsure who owns memory, that is a sign to redesign the interface.
Modern C++ and Rust make this automatic. C++'s unique_ptr and shared_ptr free memory when the pointer goes out of scope. Rust's ownership system enforces the single-owner rule at compile time. Understanding the manual version in C makes those mechanisms much easier to appreciate.
Where to go next
Next: buffer overflows — the other major memory corruption bug, where you write past the end of a buffer and corrupt adjacent memory.
malloc, calloc, realloc, and free
Allocate and manage heap memory in C using malloc, calloc, realloc, and free — including correct usage patterns and common mistakes.
Buffer overflows
Understand buffer overflows — how they happen, why they are security vulnerabilities, and the defensive practices that prevent them.