Menu

Stack vs Heap in C: Lifetimes, Dangling Pointers, and When to Use Which

Where your data actually lives: automatic storage on the stack, dynamic storage on the heap, and static storage as the third region - with the classic dangling-pointer bug from returning a local, and a rule for choosing.

This page includes runnable editors - edit, run, and see output instantly.

Every variable in a C program lives somewhere, and where determines two things you cannot change afterwards: how long it survives, and how much of it you can have. C gives you three storage regions, and choosing wrongly produces either a crash or a leak. This page lays them out and shows the classic bug that comes from getting lifetimes wrong.

The Three Regions

  high addresses
  +---------------------------+
  |  stack                    |  locals, parameters, return addresses
  |    grows downward  |      |  freed automatically on return
  |                    v      |
  +---------------------------+
  |         (unused gap)      |
  +---------------------------+
  |                    ^      |
  |    grows upward    |      |
  |  heap                     |  malloc / calloc / realloc blocks
  +---------------------------+  freed only by free()
  |  static / global data     |  globals and statics, whole run
  +---------------------------+
  |  code (text)              |  the machine code, read-only
  +---------------------------+
  low addresses
  • Automatic storage (the stack) holds function parameters and non-static locals. A block of stack is claimed when a function is entered and released when it returns. Size is fixed at compile time.
  • Dynamic storage (the heap) holds everything from malloc, calloc, and realloc. Size is decided at runtime; lifetime ends only at free.
  • Static storage holds globals and anything declared static. It exists for the whole run of the program and is zero-initialized before main starts.

The diagram's addresses are the usual arrangement, not a guarantee - the standard describes lifetimes, not layout.

Automatic Storage in Action

Each call to demo gets a fresh local and a fresh table. Nothing is freed by hand, nothing can leak, and the allocation costs a single instruction that moves the stack pointer. This is why ordinary locals should be your default: they are the fastest and the safest storage C has.

The catch is the closing brace. Once it runs, that memory is gone.

The Dangling Pointer

Here is the bug every C programmer writes once:

/* BROKEN: returns the address of memory that no longer exists */
int *make_counter(void) {
    int count = 0;
    return &count;          /* count dies at this brace */
}

int main(void) {
    int *p = make_counter();
    *p = 5;                 /* writing into a dead stack frame */
    return 0;
}

&count was a perfectly valid address while make_counter was running. On return, that stack space is handed to whatever function is called next, so p now points into someone else's local variables. Reading gives garbage; writing corrupts them. GCC and Clang warn about this exact shape (-Wreturn-local-addr), so compile with warnings on.

The same bug wears a disguise with arrays, and there the warning often does not fire:

The broken version of that function would build the text in a local char buf[64] and return buf; - returning the address of a buffer that ceases to exist at the same instant.

Three Ways to Fix It

1. The caller supplies the buffer (shown above). No allocation, no ownership question, and the most common style in C libraries. The function takes the size so it can stay inside it.

2. Return heap memory, and say who frees it.

The heap block outlives the function by design - that is the whole point of dynamic memory. The cost is the ownership comment and the caller's free.

3. Use static storage, when a single shared buffer is acceptable:

static inside a function keeps the variable's scope local while giving it the program's lifetime, so returning its address is legal. The trade is that there is only ever one of it: every caller shares it, which makes this pattern unusable in threaded code and surprising even in single-threaded code when two callers hold the pointer at once.

Size: Where the Stack Runs Out

Stack space is small and fixed. The main thread typically gets 1 MB on Windows and 8 MB on Linux; a spawned thread often gets far less. The heap is bounded by the system's available memory.

void bad(void) {
    int huge[1000000];      /* ~4 MB of stack - likely crashes on entry */
    huge[0] = 1;
}

There is no diagnostic and no NULL to check: the program simply dies, usually with a segmentation fault, before the first line of the body runs. The heap version reports failure properly:

Deep recursion exhausts the stack the same way, one frame at a time - a runaway recursive function is the most common cause of a stack overflow in practice.

Cost and Locality

Stack allocation is one arithmetic operation on a register. Heap allocation is a library call that searches for a suitable block, may take a lock, and occasionally asks the operating system for more memory. In a hot loop that difference is measurable.

Stack data is also compact and recently touched, so it tends to be in cache. Heap blocks can be scattered. Neither fact should drive a design on its own - correctness of lifetime comes first - but between two designs that are both correct, the stack one is usually the faster one.

Seeing the Regions

Printing addresses makes the layout concrete. The exact values differ every run (modern systems randomize them), but the grouping is visible:

The global and the static sit next to each other; the heap block is elsewhere; the local is typically far from both. Cast to void * for %p - that is what the format specifier requires.

Choosing

Use the stack when:

  • the size is known at compile time,
  • the data is only needed inside this function and the ones it calls,
  • and it is small - a few kilobytes, not megabytes.

Use the heap when:

  • the size depends on input, a file, or a computation,
  • the data must outlive the function that created it,
  • or it is large enough to threaten the stack limit.

Use static when:

  • exactly one instance should exist for the whole program,
  • and sharing it between all callers is genuinely correct.

The default is the stack. Reach for the heap when one of its three reasons applies, and when you do, follow the ownership rules in memory leaks so the block you gained a lifetime for still gets released.

Two Mirror-Image Bugs

They are worth naming together, because they are the same lifetime question answered two ways:

  • Dangling pointer - the memory died before the pointer did. Returning &local, or using a pointer after free. The program reads or writes storage that now belongs to something else.
  • Memory leak - the pointer died before the memory did. Losing the last reference to a malloc block. Nothing breaks immediately; the process just grows.

Both come from a mismatch between how long the data must live and which region you put it in. Decide the lifetime first, and the region follows.

Frequently Asked Questions

What is the difference between the stack and the heap in C?

The stack holds local variables: the compiler sizes them, they are created when a function is entered and destroyed when it returns, and allocation costs nothing. The heap holds malloc blocks: you choose the size at runtime, the block survives until you free it, and allocation has a real cost.

Why can't I return a pointer to a local variable in C?

Because the local's storage is released the moment the function returns. The pointer still holds that address, but the memory now belongs to the next function call - reading it gives garbage, writing it corrupts unrelated data. That is a dangling pointer. Return a malloc block, or have the caller supply the buffer.

How big is the stack in C?

Typically 1 to 8 MB for the main thread, and much less for additional threads - small enough that int big[1000000]; as a local usually crashes the program on entry. The heap is limited by available system memory, so large or unknown-size data belongs there.

When should I use the heap instead of the stack in C?

Three cases: the size is not known until runtime, the data must outlive the function that created it, or the block is too large for the stack (roughly anything over a few hundred kilobytes). Everything else should be a plain local - it is faster and it cannot leak.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED