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>)

FunctionDescription
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. malloc is 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