C Memory Management Cheat Sheet
Covers dynamic memory allocation, deallocation, common pitfalls like leaks and dangling pointers, and debugging tools for safe C memory management.
malloc, calloc, realloc, free
Core heap allocation functions from stdlib.h.
#include <stdlib.h>int *arr = malloc(10 * sizeof(int)); // Allocate 10 ints (uninitialized)if (arr == NULL) { // Always check for allocation failure // handle error}int *zeroed = calloc(10, sizeof(int)); // Allocate 10 ints, zero-initializedarr = realloc(arr, 20 * sizeof(int)); // Resize to 20 ints (may move the block)free(arr); // Release memory back to the heaparr = NULL; // Avoid leaving a dangling pointer
Stack vs Heap
Where variables live and who is responsible for freeing them.
void stack_example(void) { int x = 42; // Stored on the stack, freed automatically on return static int y = 0; // Stored in the static/data segment, persists across calls}void heap_example(void) { int *p = malloc(sizeof(int)); // Stored on the heap, lives until free() *p = 42; free(p); // Caller is responsible for freeing}
Common Pitfalls
Memory bugs that are easy to introduce and hard to debug.
- Memory leak- Forgetting to free() heap memory before losing the last pointer to it
- Dangling pointer- Using a pointer after the memory it points to has been freed
- Double free- Calling free() twice on the same pointer; corrupts the heap allocator
- Buffer overflow- Writing past the bounds of an allocated block, corrupting adjacent memory
- Use-after-free- Reading or writing memory through a pointer after free() was called
- Uninitialized read- Reading a malloc'd block before writing to it (malloc does not zero memory)
- Alignment- malloc guarantees alignment suitable for storing any standard C type
Debugging with Valgrind
Detect leaks and invalid memory access at runtime.
gcc -g -o app app.c # Compile with debug symbolsvalgrind --leak-check=full ./app # Detect leaks and invalid accessvalgrind --track-origins=yes ./app # Trace origin of uninitialized values
Aligned & Sized Allocation
Request memory with a specific alignment for SIMD, cache lines, or hardware buffers.
#include <stdlib.h>// C11: size must be a multiple of alignmentvoid *buf = aligned_alloc(64, 256); // 64-byte aligned, 256 bytesif (buf == NULL) { // handle failure}free(buf); // ordinary free() works with aligned_alloc// POSIX alternative available on most platformsvoid *buf2;if (posix_memalign(&buf2, 64, 256) != 0) { // handle failure}free(buf2);
Using realloc() Safely
Avoid the classic bug of losing the original pointer on allocation failure.
int *arr = malloc(10 * sizeof(int));// WRONG: if realloc fails, this leaks the original block// arr = realloc(arr, 20 * sizeof(int));// RIGHT: use a temporary so the original pointer survives failureint *tmp = realloc(arr, 20 * sizeof(int));if (tmp == NULL) { // arr is still valid here, handle the error, then free(arr) if giving up free(arr); return NULL;}arr = tmp;// realloc(ptr, 0) is implementation-defined since C23 deprecated it -// prefer an explicit free(ptr) when shrinking to zero
Simple Arena (Bump) Allocator
Allocate many small objects from one block and free them all at once.
typedef struct { unsigned char *base; size_t capacity; size_t offset;} Arena;void arena_init(Arena *a, size_t size) { a->base = malloc(size); a->capacity = size; a->offset = 0;}void *arena_alloc(Arena *a, size_t size) { size_t aligned = (a->offset + 7) & ~((size_t)7); // 8-byte align if (aligned + size > a->capacity) return NULL; // out of space void *p = a->base + aligned; a->offset = aligned + size; return p;}void arena_reset(Arena *a) { a->offset = 0; } // "free" everything at oncevoid arena_destroy(Arena *a) { free(a->base); }
Catching Bugs with Sanitizers
Compiler-instrumented runtime checks that catch memory errors valgrind may miss or run faster than.
# AddressSanitizer: detects use-after-free, buffer overflow, double freegcc -fsanitize=address -g -o app app.c./app# UndefinedBehaviorSanitizer: detects signed overflow, misaligned access, etc.gcc -fsanitize=undefined -g -o app app.c# combine both, plus leak detection (on by default with ASan on Linux)gcc -fsanitize=address,undefined -g -o app app.cASAN_OPTIONS=detect_leaks=1 ./app
Memory Layout & Ownership Concepts
Vocabulary for reasoning about where data lives and who owns it.
- BSS segment- Holds zero-initialized and uninitialized static/global variables; occupies no space in the executable file, only at runtime.
- Heap fragmentation- Repeated alloc/free of varying sizes leaves gaps too small to satisfy later requests, wasting address space.
- Ownership convention- A documented rule (e.g. 'caller frees') for which code is responsible for calling free() on a given pointer, since C has no automatic tracking.
- RAII substitute- goto cleanup patterns or __attribute__((cleanup(...))) (GCC/Clang) approximate deterministic cleanup that C lacks natively.
- Memory pool- Pre-allocates fixed-size blocks up front to avoid malloc/free overhead and fragmentation in hot paths.
- mmap for large allocations- glibc's malloc automatically routes very large requests through mmap() instead of the heap, returning memory directly to the OS on free().
Set pointers to NULL immediately after free() so accidental reuse crashes predictably instead of causing silent heap corruption that surfaces far from the actual bug.