不能用try-catch捕获SIGSEGV,因其是操作系统信号而非C++异常;需用sigaction()注册异步信号安全的处理器,且捕获后不可安全恢复执行,预防(如AddressSanitizer、智能指针)远比捕获重要。

为什么不能用 try-catch 捕获 SIGSEGV
段错误(SIGSEGV)是操作系统发给进程的信号,不是 C++ 异常,try/catch 完全无效。强行 throw 在信号处理函数里还可能引发未定义行为——比如栈已损坏,throw 本身就会再崩一次。
用 signal() 或 sigaction() 注册处理器
必须用 POSIX 信号 API 拦截 SIGSEGV。优先选 sigaction():它更可靠、可屏蔽嵌套信号、能保留原有上下文。
常见错误是只调用 signal(SIGSEGV, handler),但该函数在不同系统上语义不一致(比如 Linux 默认重置 handler,而 macOS 可能不重置),且无法控制信号掩码。
-
sigaction()的sa_flags至少要加SA_RESETHAND(防止 handler 被自动清除)和SA_NODEFER(避免递归触发时被阻塞) - handler 函数签名必须是
void handler(int sig, siginfo_t *info, void *ucontext),且需声明为static或全局,不能是类成员函数 - 不要在 handler 里调用非异步信号安全函数(如
printf、malloc、std::string::append),否则行为未定义
struct sigaction sa;
sa.sa_sigaction = [](int sig, siginfo_t *info, void *ucontext) {
// 仅可用 write(), _exit(), siglongjmp() 等 async-signal-safe 函数
const char msg[] = "Caught SIGSEGV\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
_exit(1); // 不要用 exit()!它不是 async-signal-safe
};
sa.sa_flags = SA_SIGINFO | SA_RESETHAND | SA_NODEFER;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, nullptr);
捕获后能“恢复执行”吗?
严格来说不能安全恢复。段错误通常意味着访问了非法地址(空指针、野指针、栈溢出等),即使你用 siglongjmp() 跳回,原上下文很可能已破坏——寄存器、栈帧、对象状态都不可信。
- 唯一较稳妥的做法是记录现场(如
info->si_addr、寄存器快照)后干净退出,或 fork 子进程做 dump - 某些场景(如 JIT 编译器、调试器)会结合
mmap()+PROT_NONE+sigaltstack()实现受控页错误处理,但这需要精确控制内存布局和信号栈,远超一般需求 - 别尝试在 handler 里修改
ucontext强行跳过出错指令——x86-64 下指令长度可变,无法安全计算下一条地址
真正该做的:预防比捕获重要得多
信号处理只是最后兜底,99% 的 SIGSEGV 都该在开发阶段消灭。工具比 handler 更有效:
- 启用 AddressSanitizer:
g++ -fsanitize=address -g,它能在非法访问发生时立刻报错并显示调用栈 - 静态检查:Clang Static Analyzer、Cppcheck 能发现空指针解引用、数组越界等模式
- 用
std::unique_ptr/std::shared_ptr替代裸指针,配合 RAII 避免悬垂指针 - 对用户输入的指针/索引做边界检查,尤其在 C 风格接口(如
memcpy、strlen)前
写信号 handler 很容易让你误以为问题已解决,其实只是把崩溃延迟到更难复现的时刻。真正关键的是让段错误根本不发生。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











