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