C++ Stack Overflow: Recursion, Large Locals, and How to Fix Crashes

C++ Stack Overflow: Recursion, Large Locals, and How to Fix Crashes

이 글의 핵심

Fix stack overflows: add base cases, move big buffers to the heap, replace deep recursion with loops or explicit stacks, and understand platform stack limits.

Stack exhaustion often shows up as a segfault—compare segfault (deep dive) and segfault (checklist).

Introduction: “My recursive function crashes”

“Segfault but I’m not using pointers”

Stack overflow happens when stack memory is exhausted. Common causes: infinite recursion, very large locals, and very deep recursion.

void foo() {
    foo();
}

int main() {
    foo();
}
// Linux/macOS: SIGSEGV; Windows: stack overflow

This article covers:

  • Four major causes
  • Recursion depth limits
  • Adjusting stack size (last resort)
  • Moving work to the heap
  • Tail recursion and iterative forms

Table of contents

  1. What is stack overflow?
  2. Four major causes
  3. Limiting recursion depth
  4. Stack size settings
  5. Heap allocation
  6. Tail recursion
  7. Summary

1. What is stack overflow?

Stack memory

Each function call creates a stack frame for locals and return addresses.

Typical default stack limits (order of magnitude):

  • Linux: ~8 MB
  • Windows: ~1 MB (varies)
  • macOS: ~8 MB

When recursion or locals exceed the limit, the program crashes.


2. Four major causes

1. Infinite recursion

int factorial(int n) {
    return n * factorial(n - 1); // missing base case
}

Fix: Base case.

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

2. Large local arrays

void process() {
    int bigArray[1000000]; // ~4 MB on stack—may overflow
}

Fix:

void process() {
    std::vector<int> bigArray(1000000);
}

3. Very deep naive recursion (e.g. Fibonacci)

Fix: Iteration or memoization.

4. Deep call chains with large frames

Move big temporaries to the heap or reuse buffers.


3. Limiting recursion depth

int factorial(int n, int depth = 0) {
    const int MAX_DEPTH = 1000;
    if (depth > MAX_DEPTH) {
        throw std::runtime_error("Recursion too deep");
    }
    if (n <= 1) return 1;
    return n * factorial(n - 1, depth + 1);
}

Prefer loops when depth can be large.


4. Stack size

Linux: ulimit

ulimit -s       # size in KB
ulimit -s 16384 # e.g. 16 MB
ulimit -s unlimited  # use with care

Windows linker

Linker → System → Stack Reserve (Visual Studio), or:

/STACK:16777216

CMake (examples)

# Linux ELF (toolchain-specific)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-z,stack-size=16777216")

# MSVC
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /STACK:16777216")

5. Heap instead of stack

void process() {
    std::vector<int> big(1000000);
    // or
    auto big = std::make_unique<int[]>(1000000);
}

Tree traversal: explicit std::stack

Replace deep recursion with an iterative walk using a container on the heap when depth is unbounded (e.g. deeply nested JSON).


6. Tail recursion

Non-tail (work after the call):

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

Tail form (call is last):

int factorialTail(int n, int acc = 1) {
    if (n <= 1) return acc;
    return factorialTail(n - 1, n * acc);
}

Inspect assembly at -O2 to see if the compiler optimized to a loop.


Case studies (sketches)

JSON parsing

Deep nesting can blow the stack with naive recursion; use an explicit stack or iteration.

Directory walk

Replace deep filesystem recursion with a directory stack/std::stack of paths.


Summary

Checklist

  • Base case for every recursive function?
  • Avoid multi-megabyte stack arrays?
  • Recursion depth bounded or converted to iteration?
  • Nested calls not multiplied by huge locals?

Mitigation priority

  1. Correct base cases
  2. Heap for large buffers
  3. Iterative algorithms / explicit stacks
  4. Tail recursion where applicable
  5. Increase stack only as a temporary measure

Rules

  1. Large arrays → vector / unique_ptr
  2. Guard recursion depth when needed
  3. Deep or unbounded recursion → iteration
  4. Consider tail-call patterns with optimizations enabled

  • Memory basics: stack vs heap
  • Segmentation fault guide
  • This article’s short segfault companion

Keywords

stack overflow, infinite recursion, recursion depth, large array, stack size, ulimit

Practical tips

Debugging

  • Add a depth counter during investigation.
  • Use backtrace to see repeated frames.
  • Valgrind/ulimit experiments can clarify stack usage.

Performance

  • Tail recursion may optimize at -O2+.
  • Loops are often faster than heavy recursion.
  • Memoization reduces call count.

Reviews

  • Verify termination for recursive functions.
  • Flag large stack allocations.
  • Question unbounded recursion depth.

Closing

Stack overflow is preventable: control recursion, avoid huge stack frames, and use the heap for large data. Increasing stack size is a last resort; fix the algorithm and memory placement first.

Next: Pair this with deeper material on recursion vs iteration and tail-call optimization in your favorite C++ resource.


  • Segmentation fault (detailed)