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 typeWhere it livesLifetime
Local variablesStackFunction scope
Function parametersStackFunction scope
Global variablesData segmentEntire program
String literalsRead-only segmentEntire program
malloc/calloc resultsHeapUntil 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