C++的try/catch无法捕获段错误等底层崩溃,因其仅处理throw抛出的异常;此类问题需用信号处理(Linux)或SEH(Windows)机制,且不可恢复执行。

崩溃不是异常,try/catch 捕不到段错误和访问违规
直接说结论:C++ 的 try/catch 只能捕获用 throw 主动抛出的 C++ 异常,对 segfault、access violation、double free 这类底层崩溃完全无效。这类问题发生在信号层(Unix/Linux 是 SIGSEGV、SIGABRT;Windows 是结构化异常 SEH),必须用信号处理或平台特定机制介入。
常见错误现象:
- 在
try块里解引用空指针,程序直接终止,catch(...)一句不执行 - 用
std::thread启动函数崩溃,主线程的try也救不了它 - 第三方库内部触发
abort(),catch无感知
实操建议:
- Linux/macOS 下用
signal(SIGSEGV, handler)注册信号处理器,但注意:信号处理函数内只能调用异步信号安全函数(write、_exit可用,printf、malloc不可用) - Windows 下用
SetUnhandledExceptionFilter捕获 SEH,再用MiniDumpWriteDump写崩溃转储 - 不要在信号/SEH 处理器里尝试“恢复执行”——栈已损坏,继续跑大概率二次崩溃
什么时候该用 std::set_terminate?
当未捕获的 C++ 异常穿透到 main 函数外时,会调用 std::terminate。默认行为是调用 std::abort,导致进程退出且无堆栈信息。这不是崩溃捕获,而是异常兜底出口。
立即学习“C++免费学习笔记(深入)”;
使用场景:
- 确保所有未处理异常至少留下日志(比如记录异常类型和当前线程 ID)
- 配合调试器,在开发环境触发断点(
__debugbreak()或raise(SIGTRAP))
实操建议:
- 用
std::set_terminate设置自定义终止函数,但函数内禁止抛出新异常(否则直接调用std::abort) - 避免在其中做复杂操作:不能用
std::cout(可能已被析构)、不能分配内存、不能锁 mutex(死锁风险) - 示例精简写法:
void my_terminate() { write(STDERR_FILENO, "Uncaught C++ exception\n", 23); _exit(1); } std::set_terminate(my_terminate);
std::unexpected 已被弃用,别碰它
C++11 起,动态异常规范(如 void foo() throw(std::runtime_error))已被废弃,std::unexpected 和相关机制彻底失效。现代编译器(GCC/Clang/MSVC)遇到带 throw(...) 声明的函数抛出声明外异常时,直接调用 std::terminate,跳过 std::unexpected。
为什么容易踩坑:
- 老项目代码或过时教程还在用
throw(),误以为能靠std::set_unexpected拦截 - 启用
-Wdeprecated编译选项也未必报错,尤其在 C++14 模式下兼容旧语法
实操建议:
- 一律改用
noexcept替代throw();noexcept(false)等价于不写 - 如果真需要运行时检查异常类型,自己封装
try/catch+ 类型判断,别依赖语言级机制
健壮性设计真正要做的三件事
指望一套“万能崩溃捕获”机制是错的。生产环境健壮性靠的是分层防御,而不是某个神奇函数。
关键动作:
- 用 AddressSanitizer(
-fsanitize=address)和 UndefinedBehaviorSanitizer 在开发/测试阶段暴露内存错误,比线上捕获崩溃更有价值 - 对关键路径做输入校验(比如指针非空、容器索引在
[0, size())内),把崩溃前移到可诊断的assert或throw - 进程级隔离:高风险模块(如解析第三方二进制格式)放进独立子进程,主进程用
waitpid监控其退出状态,崩溃不影响主流程
最容易被忽略的一点:信号处理函数里无法安全调用 backtrace() 或 abi::__cxa_demangle —— 它们依赖 malloc 和符号表,而崩溃时刻这些设施本身可能已不可用。真要堆栈,得用 libunwind 配合预先映射的只读符号缓存,或者直接依赖 gdb --batch 离线分析 core dump。










