栈溢出是函数调用帧压栈导致空间耗尽的崩溃,无C++异常、try/catch无效;快速确认法:ulimit -s查限制,gdb bt看调用深度——上百层重复调用即递归失控,2–5层且访问大局部数组则单帧过大,多线程中报内存访问失败需查线程栈大小。

栈溢出不是内存泄漏,也不是 new 失败,它发生在函数调用帧压栈时空间耗尽——崩溃前你不会看到任何 C++ 异常,try/catch 完全无效。
怎么快速确认是栈溢出而不是其他崩溃?
别靠日志猜。Linux 下立刻执行:ulimit -s 看当前栈限制(单位 KB),再运行程序并生成 core dump;然后用 gdb ./a.out core → bt:
- 如果
bt输出显示上百甚至上千层重复函数调用(如parse_node → parse_node → parse_node…),基本锁定递归失控 - 如果
bt只有 2–5 层,但崩溃点在局部大数组访问(如buf[1000000]),大概率是单帧栈变量过大 - 若只在多线程中崩溃,且
bt深度正常但报Cannot access memory at address,要查std::thread默认栈大小(Linux 通常仅 2MB)
递归函数必须加 depth 参数和硬性阈值
这不是“防御性编程”,而是防止上线后因输入数据意外变深而静默崩溃。不要依赖编译器尾递归优化——return f(n-1) + 1 这类写法根本不会被 GCC/Clang 优化成循环。
- 在递归函数签名里显式加入
int depth = 0参数,并在入口处检查:if (depth > 1000) throw std::runtime_error("recursion too deep"); - 阈值设多少?主线程下保守取 1000;若已知栈限制为 8MB,单帧约 4KB 开销,则上限 ≈ 2000,但留 50% 余量更安全
- 避免在递归体里定义大对象(如
std::string temp = heavy_computation();),改用引用传参或提前计算好传入
大局部变量必须移出栈空间
char buf[65536] 看似只是个数组,但它直接吃掉 64KB 栈空间;10 层递归就干掉 640KB。这类变量几乎从不值得留在栈上。
立即学习“C++免费学习笔记(深入)”;
- 用
std::vector<char> buf(65536)</char>替代——数据在堆,栈上只存 3 个指针(24 字节) - 若需固定大小且性能敏感,改用
std::unique_ptr<char> buf = std::make_unique<char>(65536)</char></char> -
std::array<char></char>仍是纯栈变量,和原生数组一样危险,别被名字误导 - 结构体传参务必用
const T&,而非T值拷贝——尤其当结构体含std::vector或std::string时,拷贝会触发额外栈分配
迭代改写比调 ulimit -s 更可靠
临时扩栈(如 ulimit -s 65536)只能帮你定位问题,不能用于生产环境:容器平台通常禁用该设置,且每个 std::thread 的栈仍独立受限。
- 把递归逻辑拆成「状态 + 动作」:把原递归参数、中间结果打包进 struct,用
std::stack<MyState>或std::vector<MyState>存储 - 用
while (!stack.empty())循环处理,每次 pop 一个状态,push 新状态(如有) - 注意:不要在循环里频繁
new/delete;std::vector配合.reserve()比std::stack更易控制内存局部性
真正容易被忽略的是:栈帧大小不仅取决于你写的变量,还受 ABI、调试信息、编译器内联策略影响——同一份代码在 -O0 和 -O2 下栈消耗可能差 2 倍。所以压测必须用发布构建配置跑。


















