CMemory Leaks

Memory Leaks

C gives you full control over dynamic memory: you decide exactly when to allocate it and exactly when to give it back with free. That control is also the risk — if you lose the only pointer to a block of allocated memory before freeing it, that memory can never be freed again for the rest of the program's run. This is a memory leak.

What a leak actually is

A leak has two ingredients: memory that was allocated (with malloc, calloc, or realloc), and the loss of every pointer that referred to it, before free was called. Once that happens, the memory is still marked "in use" from the operating system's point of view, but your program has no way left to reach it or free it.

C
#include <stdlib.h>

void leaky_function(void) {
    int *data = malloc(100 * sizeof(int));
    // ... use data ...

    // BUG: function returns without calling free(data).
    // data was the only pointer to that block, and it just went out
    // of scope. The 400 bytes it pointed to are now unreachable.
}

int main(void) {
    for (int i = 0; i < 1000; i++) {
        leaky_function();   // leaks 400 bytes every single call
    }
    return 0;
}
A leak is not an immediate crash
Nothing crashes when a leak happens. The program keeps running correctly in every visible way — which is exactly what makes leaks dangerous. The damage accumulates silently.
Why leaks matter more in long-running programs

A short command-line tool that leaks a few bytes and then exits is harmless in practice — the operating system reclaims all of a process's memory when it terminates. The real danger is in long-running programs: servers, daemons, games, embedded systems, anything that stays alive for hours, days, or months. In those programs, even a small leak that happens repeatedly (once per request, once per frame, once per loop iteration) steadily grows the process's memory footprint until it exhausts available memory, causing severe slowdowns or crashes far from where the actual bug is.

Finding leaks with Valgrind

Leaks are notoriously hard to spot by reading code alone, because the bug is an absence — a free that should exist but does not. Tools like Valgrind's memcheck run your compiled program and track every allocation and free at runtime, then report exactly which allocations were never freed, along with the call stack that created them. Running a C program under Valgrind periodically during development is one of the most effective habits for catching leaks early, and is well worth learning as a follow-up to this page.

Bash
valgrind --leak-check=full ./my_program
==12345== HEAP SUMMARY:
==12345==     in use at exit: 400 bytes in 1 blocks
==12345== LEAK SUMMARY:
==12345==    definitely lost: 400 bytes in 1 blocks
==12345==      at 0x...: malloc
==12345==      by 0x...: leaky_function
Fixing the leak

The fix is simply to free the memory before the last pointer to it disappears:

C
#include <stdlib.h>

void not_leaky(void) {
    int *data = malloc(100 * sizeof(int));
    // ... use data ...

    free(data);   // give the memory back before data goes out of scope
}
Know where every free() will happen before you write malloc()
For every `malloc` (or `calloc`/`realloc`), decide up front exactly where the matching `free` will occur — the end of a function, a cleanup block, a destructor-like helper — before you write the allocation itself. Treating allocation and deallocation as a single matched pair, rather than writing the `malloc` now and figuring out the `free` later, is the single most effective discipline for avoiding leaks in C.
Watch early returns and error paths
A very common way leaks sneak in: a function allocates memory, then has an early `return` on an error path that skips the `free`. Every exit point from a function that owns allocated memory needs to either free it or hand ownership off (e.g. through an output parameter) before returning.
  • A leak happens when allocated memory is never freed because every pointer to it is lost.

  • Leaks do not crash immediately — they accumulate silently, which is what makes them dangerous in long-running programs.

  • Valgrind's memcheck can run a program and report exactly which allocations were never freed.

  • Plan the matching free for every malloc before writing the allocation, including on early-return error paths.