Quick Answer

The stack holds function frames: parameters, local variables and return addresses. It is fast because allocating is just moving a register, and limited because each thread gets one fixed block, commonly a few megabytes. The heap is a large pool you allocate from explicitly and must release, which is where leaks come from. Deep recursion or one huge local array pushes past the stack limit and the program dies, usually reported as a segmentation fault.

Two regions with completely different rules

When your program runs, the operating system hands it a block of address space that gets divided into regions. Two of them matter for day-to-day programming.

The stack is a last-in-first-out region managed automatically. Every time a function is called, a frame is pushed onto it holding that call's parameters, its local variables, and the return address telling the CPU where to resume. When the function returns, the whole frame is discarded in one step. You never write code to manage this; the compiler emits it.

The heap is a large pool you allocate from on request, with new in C++ or malloc in C, and which stays yours until you release it. It has no automatic lifetime, which is both the point and the problem: an object on the heap can outlive the function that created it, and it will keep existing after you stop caring about it unless something frees it.

Two other regions are worth naming because variables live there too. Globals and static variables sit in a data segment that exists for the entire run of the program, neither stack nor heap. Your compiled instructions live in a text segment, and string literals usually sit in read-only memory near it, which is why writing through a char* that points at a literal crashes rather than silently editing it.

The distinction to hold onto is lifetime, not location. Stack means the compiler knows exactly when this dies. Heap means you decided when it dies. Everything else follows from that.

What actually lives where

Here is the layout for one small function, annotated:

#include <vector>

int total = 0;                   // data segment, lives for the whole program
static int callCount = 0;        // data segment

void f(int n) {                  // n: stack (parameter)
    int x = 5;                   // stack, 4 bytes
    int arr[100];                // stack, 400 bytes
    int* p = new int[100];       // p on the stack, the 400 bytes on the heap
    std::vector<int> v(100);     // v on the stack, its buffer on the heap

    delete[] p;                  // v needs no such line
}

The line worth pausing on is the last pair. A std::vector object is small, typically three pointers, and it sits on the stack like any other local. The elements it holds are on the heap. Same for std::string once the text is longer than the small buffer stored inside the string object itself.

That split explains something that otherwise looks arbitrary. This crashes:

void f() { int arr[1000000]; arr[0] = 1; }              // ~4 MB on the stack

and this is completely routine:

void g() { std::vector<int> arr(1000000); arr[0] = 1; } // ~4 MB on the heap

The stray write in each is only there so that the optimiser cannot delete an array nobody reads. Both hold a million integers. The first tries to reserve four megabytes inside a stack frame and blows past the limit. The second puts a small object on the stack whose buffer comes from the heap, where four megabytes is nothing.

This is also why competitive programmers declare large arrays at global scope. A global int arr[10000000] lives in the data segment, not the stack, so the same declaration that kills a local works fine outside any function, and it is zero-initialised for free.

Why the stack is fast and why it is small

Stack allocation is close to free. The CPU keeps a stack pointer in a register, and making room for a frame means subtracting a constant from it, usually one instruction. Releasing the frame is adding it back. There is no search, no bookkeeping, no metadata, and no possibility of fragmentation, because memory is always taken and returned at one end.

It is also fast for a second reason that matters more than the instruction count: locality. Your program keeps reusing the same few kilobytes at the top of the stack, so those lines are almost always sitting in the CPU's L1 cache. A heap allocation, by contrast, may land anywhere, and a cache miss costs far more than the allocation itself.

Heap allocation has to do real work. The allocator maintains a structure of free blocks, searches it for one large enough, splits it, records the size so free knows what to release, and in a multithreaded program may take a lock. If it has no suitable block it asks the operating system for more memory. Modern allocators are good, with per-thread caches for small sizes, but even their fast path is far more work than adjusting a register, and the slow path that goes back to the kernel is worse again.

The price of that speed is a hard ceiling. Each thread is given one stack when it is created and it cannot grow on demand. On many Linux systems the main thread gets 8 MB; you can see yours with ulimit -s, which prints kilobytes. On Windows the default is typically 1 MB, fixed in the executable header at link time. Threads you create yourself usually get less, and the size is a parameter of thread creation. The heap, by contrast, is bounded by the address space and available RAM.

What a stack overflow actually is

A stack overflow is not a mysterious error. It is the stack pointer walking past the end of the block your thread was given. Two things cause it.

The first is recursion without a working base case, or with a base case that some input never reaches:

int depth(int n) {
    return depth(n + 1);    // no base case: a new frame every call
}

Build that with optimisation off if you want to watch it fail. This particular call sits in tail position, so at -O2 the compiler is free to turn it into a plain infinite loop that never grows the stack at all.

Do the arithmetic and the scale becomes concrete. If a frame takes 64 bytes and the stack is 8 MB, you get somewhere in the region of a hundred thousand nested calls before it ends. A frame holding a few local arrays is far larger, so the real number can be much smaller. Either way it is a fixed budget, not a slope you gradually slide down.

The second cause is a single frame that is too big on its own, which is the int arr[1000000] case from earlier. One call is enough.

What you see depends on the platform, and this trips people up. On Linux the guard page below the stack is unmapped, so the process receives SIGSEGV and the terminal prints Segmentation fault. There is no message saying stack overflow. On Windows you get a stack overflow exception, and a debugger will name it. If a program with recursion in it segfaults immediately on a large input, unbounded recursion is the first thing to check.

Managed languages behave differently because their runtime counts depth for you. Java raises StackOverflowError, and CPython raises RecursionError at a default limit of 1000 frames, adjustable with sys.setrecursionlimit. That limit is a safety net, not the real stack, so raising it far enough can crash the interpreter outright for the same underlying reason.

The fixes are the usual three: add or correct the base case, move large buffers off the stack into a std::vector or a global array, or rewrite the recursion iteratively with an explicit stack. A depth-first search over a graph of a hundred thousand nodes that happens to form a long chain is the classic case where the iterative version is not optional.

Leaks, fragmentation and who cleans up

Heap memory has the opposite failure mode. Nothing reclaims it automatically, so a leak is simply memory you allocated and then lost the last pointer to. Neither you nor the allocator can ever free it again.

void loadStudents() {
    int* marks = new int[1000];
    // ... work ...
}                                // marks goes out of scope; the 4000 bytes stay

The pointer was on the stack and died with the frame. The allocation it referred to did not. How much this matters depends entirely on how long the process lives. A competitive programming solution that runs for two seconds and exits can leak everything it likes, because the operating system reclaims the whole address space on exit. A web server or a game loop that leaks a little per request climbs until the machine starts swapping.

Fragmentation is the subtler cousin. After a long run of mixed allocations and frees, the free space is real but chopped into pieces, so a large request can fail even though the total free memory is ample. Long-running processes are the ones that feel this, which is why pooling and reusing buffers is common in servers and games.

Do not hunt leaks by eye. On Linux, valgrind --leak-check=full ./a.out reports every unfreed block with the stack trace that allocated it. GCC and Clang also support -fsanitize=address, which catches leaks along with out-of-bounds writes and use-after-free, and it is worth turning on for every debug build. MSVC has its own debug heap facilities.

The better answer is to not manage it manually. Use std::vector and std::string instead of raw arrays, and std::unique_ptr when you really need heap objects. Their destructors run when the owning stack frame unwinds, including during an exception, so the stack ends up managing the heap for you. And note that garbage-collected languages are not immune: a Java HashMap or a JavaScript array that only ever grows is unreachable memory in every practical sense, and it will exhaust the heap just as effectively.

Frequently Asked Questions

How do I know if a variable is on the stack or the heap? Local variables and function parameters are on the stack. Anything created with new or malloc is on the heap. Globals and static variables are in a separate data segment that lives for the whole program. Container objects straddle the line: a local std::vector sits on the stack while the elements it holds are on the heap, which is why a local vector of a million integers is fine but a local array of a million integers is not.
Why is the stack limited to only a few megabytes? Because each thread needs its own stack and the size is fixed when the thread is created, so the region must be reserved up front rather than grown on demand. A server running thousands of threads with a large stack each would reserve an enormous amount of address space for memory almost none of them use. The limit is a deliberate trade: small and fast per thread, with the heap available for anything large.
Why does infinite recursion print segmentation fault instead of stack overflow? On Linux there is an unmapped guard page below the stack, so running off the end is just an invalid memory access and the kernel delivers SIGSEGV, which the shell reports as a segmentation fault. There is no separate stack overflow signal. On Windows you get a distinct stack overflow exception instead. Same underlying cause, different report, which is why the message can be misleading.
Is heap allocation really that much slower than stack allocation? Yes for a single allocation, because the stack only moves a register while the heap allocator must find a suitable free block, record its size and possibly take a lock or ask the operating system for more memory. The bigger cost is often locality: repeated heap allocations scatter data across memory and cause cache misses. Allocate once outside a hot loop rather than allocating inside it.
Do memory leaks matter if the program exits quickly? Not much in practice. The operating system reclaims the whole address space when the process ends, so a short-lived script or a contest submission is not harmed by leaking. It matters in anything long-running, such as a server, a game loop or a background service, where a small leak per request or per frame accumulates until allocation fails. Leaks also hide real ownership bugs, so it is worth fixing them regardless.