C Cheatsheet
Memory Management
Use this C reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Memory Layout of a C Program
High addresses ┌─────────────────┐ │ Stack │ Local variables, function call frames — grows down ├─────────────────┤ │ (gap) │ ├─────────────────┤ │ Heap │ malloc/calloc/realloc allocations — grows up ├─────────────────┤ │ BSS │ Uninitialized / zero-initialized globals and statics ├─────────────────┤ │ Data │ Initialized globals and statics ├─────────────────┤ │ Text │ Program code (read-only) Low addresses
Heap Allocation (<stdlib.h>)
| Function | Description |
|---|---|
malloc(size) | Allocate size bytes, uninitialized. Returns void* or NULL. |
calloc(nmemb, size) | Allocate nmemb * size bytes, zero-initialized. |
realloc(ptr, size) | Resize allocation. May move; old ptr becomes invalid. |
free(ptr) | Release memory allocated by malloc/calloc/realloc. |
#include <stdlib.h> // malloc int *arr = malloc(10 * sizeof(int)); if (!arr) { perror("malloc"); exit(EXIT_FAILURE); } arr[0] = 42; free(arr); arr = NULL; // avoid dangling pointer // calloc — returns zeroed memory int *zeros = calloc(10, sizeof(int)); // 10 ints, all 0 // realloc int *buf = malloc(5 * sizeof(int)); buf = realloc(buf, 10 * sizeof(int)); // enlarge if (!buf) { /* original freed if realloc fails? NO — original untouched */ /* but if you overwrote the pointer, you've leaked! See safe pattern below */ } // Safe realloc int *tmp = realloc(buf, new_size * sizeof(int)); if (!tmp) { free(buf); return -1; } // buf still valid if realloc failed buf = tmp; free(NULL); // always safe (no-op)
Preferred Allocation Idioms
// Use sizeof *ptr — auto-adjusts if type changes int *p = malloc(n * sizeof *p); MyStruct *s = malloc(sizeof *s); // Single struct Student *st = malloc(sizeof(Student)); // Flexible array member struct struct Header *h = malloc(sizeof(struct Header) + data_len); // Check every allocation void *p = malloc(n); if (!p) { fprintf(stderr, "OOM\n"); exit(1); }
Stack vs Heap
| | Stack | Heap | |-|-------|------| | Lifetime | Until enclosing scope ends | Until explicitly freed | | Size | Limited (typically 1–8 MB) | Limited by system RAM | | Speed | Very fast (pointer bump) | Slower (metadata bookkeeping) | | Fragmentation | None | Yes | | Overflow | Stack overflow crash | Returns NULL |
// Stack allocation int arr[1000]; // ok for small arrays int big[1000000]; // DANGER: likely stack overflow // Heap for large or variable-size data int *big = malloc(1000000 * sizeof(int));
Common Memory Errors
Memory Leak
void leak(void) { int *p = malloc(100); // forgot to free — memory leaked when p goes out of scope } // Fix void no_leak(void) { int *p = malloc(100); // ... use p ... free(p); }
Use After Free
int *p = malloc(sizeof(int)); *p = 5; free(p); *p = 10; // UB: use-after-free printf("%d", *p); // UB // Fix: set to NULL after free free(p); p = NULL; // *p; // crash — dereference NULL; visible bug now
Double Free
free(p); free(p); // UB: double free (corrupts heap) // Fix free(p); p = NULL; free(p); // free(NULL) is safe no-op
Buffer Overflow
char buf[10]; strcpy(buf, "this string is too long"); // overflow — UB // Fix strncpy(buf, src, sizeof buf - 1); buf[sizeof buf - 1] = '\0'; // or snprintf(buf, sizeof buf, "%s", src);
Returning Pointer to Local
int *bad(void) { int local = 5; return &local; // UB: local destroyed on return } // Fix: use static or heap int *ok_static(void) { static int val = 5; return &val; } int *ok_heap(void) { int *p = malloc(sizeof(int)); *p = 5; return p; // caller must free }
alloca and VLAs (Stack Dynamic)
#include <alloca.h> // POSIX / GCC extension // alloca — stack allocation, freed on function return char *buf = alloca(n); // no free needed; not in C standard // VLA (C99+) — standard alternative void func(int n) { int arr[n]; // stack; auto-freed; n <= ~1000 elements safely // ... use arr ... }
Avoid for large
n— no overflow detection.mallocis safer for anything beyond a few kilobytes.
Memory Safety Tools
# AddressSanitizer (ASAN) — detects leaks, overflow, use-after-free gcc -fsanitize=address,undefined -g -o prog main.c ./prog # Valgrind — thorough leak and error checking valgrind --leak-check=full ./prog # Memory debugging flags gcc -g -O0 ... # disable optimization for better error messages
memset, memcpy, memmove
#include <string.h> // Zero out memory memset(buf, 0, sizeof buf); // fill with byte 0 memset(buf, 0xFF, n); // fill with 0xFF bytes memset(arr, 0, n * sizeof(int)); // zero int array // Copy non-overlapping regions memcpy(dst, src, n * sizeof(int)); // UB if regions overlap // Copy with possible overlap memmove(dst, src, n * sizeof(int)); // always safe
posix_memalign / aligned_alloc (Aligned Allocation)
// C11: aligned_alloc void *p = aligned_alloc(64, 1024); // 1024 bytes aligned to 64-byte boundary free(p); // POSIX: posix_memalign void *p2; posix_memalign(&p2, 64, 1024); // returns 0 on success free(p2);
Ownership Conventions
C has no automatic memory management. Establish and document ownership clearly:
// Pattern 1: caller allocates, callee fills void fill(int *buf, int n); // Pattern 2: callee allocates, caller frees char *build_string(void); // caller must free // Pattern 3: object owns its members — destructor frees them typedef struct { char *name; int age; } Person; Person *person_new(const char *name, int age); void person_free(Person *p); // frees p->name then p