Pointers & Memory · intermediate · ~8 min
Pair every malloc with one free, and exactly one.
free(p) returns memory previously obtained from malloc, calloc, or realloc to the heap. The memory becomes available for reuse on the next allocation. p itself still holds the old address — but reading or writing through it is undefined behaviour.
Without free, every allocation is permanent. A long-running program (a server, a daemon) that leaks even a few bytes per request will eventually exhaust memory and crash. free is the discipline that keeps long-running C programs healthy.
One free per malloc. Calling free twice on the same pointer corrupts the allocator. Set to NULL after free. A freed pointer is a dangling pointer; later dereferences are use-after-free. The convention free(p); p = NULL; defangs it. Don't free pointers you didn't malloc. Stack pointers (int x; free(&x);) and string literals (free("hello");) are never valid arguments.
char *buf = malloc(64);
/* ... use buf ... */
free(buf); // hand memory back
buf = NULL; // belt-and-braces against later use-after-free
free(NULL); // explicitly safe — does nothing
void free(void *p) releases memory previously returned by malloc/calloc/realloc. The pointer becomes invalid — using it after free is undefined behaviour. free(NULL) is a safe no-op.
The C standard library has no built-in ownership rules — they're a discipline. Document who owns what. A common pattern: paired T_new() and T_free() functions in your module.
Valgrind catches free of an already-freed or never-malloc'd pointer. AddressSanitizer (compile with -fsanitize=address) catches use-after-free. If a crash happens 'far away' from the bad free, it's because the heap got corrupted earlier — the immediate crash site is just where the corruption was finally noticed.
Double-free is one of the worst bugs you can write — the allocator's internal lists corrupt and you may get a write-anywhere primitive. Use-after-free lets attackers overwrite freed memory with their own data, then exploit your stale pointer. The single best habit: free(p); p = NULL; everywhere. Every modern static analyzer warns about both patterns.
End-of-function cleanup in any C program. Reference-counted release in libraries. The 'destructor' equivalent for every malloc'd struct.
void *xmalloc(size_t n) that aborts on NULL — pair it with plain free. 2. Write a struct + its destroy function; verify with valgrind. 3. Cause a deliberate double-free under gdb to see the abort message.free ends a memory lifetime. Match every malloc with exactly one free, set the pointer to NULL after, and never free what you didn't malloc. Combined with leak detectors, these habits make C programs as reliable as garbage-collected ones.