Freeing Memory with free()
Every block of memory you obtain from
malloc(), calloc(), or realloc() comes from a shared pool called the heap, and C does not clean that pool up for you. When you are finished with a block, you must explicitly return it to the system with free(), declared in <stdlib.h>.C
void free(void *ptr);
The rule is simple to state and surprisingly easy to get wrong in practice: every successful allocation must eventually be matched with exactly one call to
free() — not zero calls (a leak), and not two or more calls (a double free).C
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = malloc(5 * sizeof(int));
if (arr == NULL) {
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i;
}
// ... use arr ...
free(arr); // exactly one matching free for the one malloc above
return 0;
}Double Free: Calling free() Twice
Calling
free() more than once on the same pointer is undefined behavior. It typically corrupts the heap's internal bookkeeping structures, which can cause a crash immediately, a crash much later in an unrelated part of the program, or — in the worst case — an exploitable security vulnerability.Double freeThe heap allocator has no way to know whether the memory pointed to by
C
int *p = malloc(sizeof(int)); free(p); free(p); // UNDEFINED BEHAVIOR: 'p' was already freed once
p has already been returned to the pool. Freeing it a second time corrupts internal allocator state, and by the time the crash actually happens, the real bug may be far away in the code and very hard to trace back.Use-After-Free: Using Memory After Freeing It
Once you call
free() on a block, that memory no longer belongs to you. The allocator is free to reuse it for a completely different allocation elsewhere in your program at any time. Reading from or writing to it afterward is undefined behavior, commonly called a use-after-free.Use-after-freeThe program might appear to "work" because the memory hasn't been reused yet — which is exactly what makes use-after-free bugs so dangerous. They can pass testing and then fail unpredictably in production once memory reuse patterns change.
C
int *p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p); // UNDEFINED BEHAVIOR: reading freed memory
*p = 100; // UNDEFINED BEHAVIOR: writing to freed memoryGood Practice: Set the Pointer to NULL After Freeing
A simple habit prevents a large fraction of double-free and use-after-free bugs: immediately after calling
free(), set the pointer to NULL.C
int *p = malloc(sizeof(int)); *p = 42; free(p); p = NULL; // now 'p' can no longer be accidentally reused // A later "free(p);" here is now harmless -- free(NULL) is well-defined // and simply does nothing. // A later "*p = 5;" here would crash immediately and obviously (NULL // dereference) instead of silently corrupting memory -- much easier to debug.
free(NULL) is explicitly safe
The C standard guarantees that calling
free() with a NULL pointer is a well-defined no-op. This is precisely what makes the "set to NULL after freeing" habit effective: it turns a dangerous double free into a harmless, do-nothing call.Think of every malloc/free as a matched pair
When writing or reviewing C code, it helps to mentally (or literally, in comments) pair up every allocation with the exact spot where it gets freed. If you cannot immediately answer "where does this get freed?" for a given allocation, that is a strong signal of a potential leak or an unclear ownership design.
Every successful malloc/calloc/realloc must eventually be paired with exactly one
free()callCalling
free()twice on the same pointer is a double free -- undefined behavior that can corrupt the heapReading or writing memory after it has been freed is a use-after-free -- also undefined behavior
Set a pointer to
NULLimmediately after freeing it --free(NULL)is guaranteed safe and a laterNULLdereference fails loudly and obviously