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
- What is stack overflow?
- Four major causes
- Limiting recursion depth
- Stack size settings
- Heap allocation
- Tail recursion
- 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
- Correct base cases
- Heap for large buffers
- Iterative algorithms / explicit stacks
- Tail recursion where applicable
- Increase stack only as a temporary measure
Rules
- Large arrays → vector / unique_ptr
- Guard recursion depth when needed
- Deep or unbounded recursion → iteration
- Consider tail-call patterns with optimizations enabled
Related posts (internal)
- 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/
ulimitexperiments 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.
More related posts
- Segmentation fault (detailed)