Every variable you declare in C lives somewhere in memory. Where it lives determines its lifetime, size limits, and who is responsible for cleaning it up. Getting this wrong is the root cause of most C memory bugs.

The Stack

The stack is a region of memory managed automatically by the CPU as functions are called and return. Each function call pushes a new stack frame containing that function’s local variables, parameters, and return address. When the function returns, the frame is popped — the memory is instantly reclaimed.

#include <stdio.h>

void func() {
    int x = 42;           /* lives on the stack */
    char buf[256];        /* 256 bytes on the stack */
    printf("x = %dn", x);
}   /* x and buf are destroyed here */

int main() {
    func();
    /* x and buf no longer exist */
    return 0;
}
Stack layout during func():

  High address
  ┌─────────────────┐
  │  main's frame   │
  │  (return addr)  │
  ├─────────────────┤
  │  func's frame   │
  │  x = 42        │ ← 4 bytes
  │  buf[256]       │ ← 256 bytes
  └─────────────────┘
  Low address

Stack characteristics

  • Automatic management — allocated on function entry, freed on return
  • Fast — just a pointer decrement to allocate
  • Limited size — typically 1–8 MB (platform-dependent)
  • No fragmentation — grows and shrinks as a contiguous block
  • Lifetime = scope — variables die when their enclosing function returns

The Heap

The heap is a large pool of memory managed manually using malloc, calloc, realloc, and free. Allocations persist until you explicitly free them — or until the program exits.

#include <stdio.h>
#include <stdlib.h>

int *create_array(int n) {
    int *arr = malloc(n * sizeof(int));   /* heap allocation */
    if (arr == NULL) return NULL;

    for (int i = 0; i < n; i++) {
        arr[i] = i * 2;
    }

    return arr;   /* safe — heap memory outlives the function */
}

int main() {
    int *data = create_array(5);
    if (data == NULL) return 1;

    for (int i = 0; i < 5; i++) {
        printf("%d ", data[i]);
    }
    printf("n");

    free(data);   /* caller is responsible */
    return 0;
}

For a full breakdown of the heap allocation functions, see our guide on malloc vs calloc vs realloc.

Heap characteristics

  • Manual management — you allocate and free it yourself
  • Slower — the allocator maintains bookkeeping structures
  • Large — limited only by available RAM and virtual memory
  • Can fragment — many alloc/free cycles leave gaps
  • Lifetime = explicit — data lives until you call free()

What Goes Where

Variable type Where it lives Lifetime
Local variables Stack Function scope
Function parameters Stack Function scope
Global variables Data segment Entire program
String literals Read-only segment Entire program
malloc/calloc results Heap Until free()

Stack Overflow

void recurse(int n) {
    char buf[4096];   /* 4KB on the stack each call */
    recurse(n + 1);   /* infinite recursion */
}
/* Stack overflow — program crashes with segfault */

Allocating large arrays on the stack, or deep/infinite recursion, exhausts the stack. The fix: allocate large buffers on the heap with malloc, or increase recursion depth limits.

Heap Bugs

When you forget to free heap memory, it accumulates until the program exits — a memory leak:

for (int i = 0; i < 1000000; i++) {
    char *buf = malloc(1024);
    /* process buf */
    /* forgot free(buf) — 1GB of leaked memory */
}

When you access heap memory after freeing it, you have a dangling pointer — one of the hardest bugs to diagnose because the crash may happen far from the actual bug.

When to Use Each

  • Use the stack when: the data is small, the lifetime matches the function, and you do not need to return it
  • Use the heap when: the size is unknown at compile time, the data must outlive the creating function, or the size is too large for the stack
/* Stack — fine for small, known-size, short-lived data */
int counts[100];
char name[64];

/* Heap — required for dynamic size or data that outlives scope */
int *arr = malloc(user_count * sizeof(int));
char *buffer = malloc(file_size + 1);

TL;DR

  • Stack: automatic, fast, limited size, lifetime = function scope
  • Heap: manual, flexible, large, lifetime = until free()
  • Local variables go on the stack — they die when the function returns
  • malloc/calloc give you heap memory — you must free it yourself
  • Large arrays belong on the heap; putting them on the stack risks overflow
  • Forgetting free() = memory leak. Using after free() = dangling pointer