signal()仅能捕获异步信号(如SIGINT、SIGTERM、SIGSEGV等),不能捕获C++异常、浮点异常或线程取消;其本质是POSIX信号机制的C封装,与try/catch无关。

signal() 函数能捕获哪些信号?
signal() 只能捕获异步信号(如 SIGINT、SIGTERM、SIGSEGV),不能捕获 C++ 异常(throw)、浮点异常(默认不触发信号)、或线程取消。它本质是 POSIX 信号机制的 C 封装,和 try/catch 完全无关。
常见可捕获信号包括:
-
SIGINT:Ctrl+C 触发 -
SIGTERM:kill默认发送 -
SIGUSR1/SIGUSR2:用户自定义用途 -
SIGSEGV、SIGABRT:可捕获但**不推荐用于错误恢复**——进入信号处理函数时栈可能已损坏,调用大多数标准库函数(如printf、std::cout)是未定义行为
如何安全注册一个 signal 处理器?
必须用 sigaction() 替代过时且不可靠的 signal()。POSIX 标准明确指出 signal() 行为在不同系统上不一致(比如是否自动重置 handler、是否阻塞同类型信号)。
正确做法:
立即学习“C++免费学习笔记(深入)”;
- 用
sigaction()显式设置sa_flags(例如SA_RESTART避免系统调用被中断) - 处理函数签名必须是
void handler(int sig) - 只在 handler 中调用异步信号安全函数(
write()、_exit()、siglongjmp()等;禁止用std::cout、malloc、pthread_mutex_lock)
struct sigaction sa;
sa.sa_handler = [](int sig) {
const char msg[] = "Caught SIGINT\n";
write(STDERR_FILENO, msg, sizeof(msg) - 1);
_exit(1); // 不要用 exit() —— 它不是 async-signal-safe
};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa, nullptr);为什么不能在 signal handler 里 throw 异常?
C++ 标准规定:从 signal handler 中 throw 会直接调用 std::terminate()。因为异常栈展开(stack unwinding)与信号中断上下文冲突,编译器无法保证对象析构或异常传播的正确性。
如果你需要“信号转异常”,只能在 handler 中设置标志位,然后由主循环轮询检测:
- 声明
volatile sig_atomic_t g_sig_received = 0;(sig_atomic_t是唯一保证原子读写的类型) - handler 中只写入
g_sig_received = sig; - 主逻辑定期检查该变量,并在安全上下文中
throw自定义异常
注意:volatile 不解决数据竞争,仅防止编译器优化掉读写;多线程下仍需配合 sigprocmask() 或 pthread_sigmask() 控制信号递送目标线程。
替代方案:更现代、更可控的信号处理方式
在支持 C++11 及以上的环境中,优先考虑:
-
std::signal()仅作兼容层,不新增功能 - 用
signalfd()(Linux 特有)将信号转为文件描述符,配合epoll统一事件循环 —— 完全规避信号 handler 的限制 - 用
sigwaitinfo()在专用线程中同步等待信号(需先用pthread_sigmask()屏蔽所有线程的信号) - 对于崩溃诊断,用
backtrace()+sigaltstack()设置备用栈,避免SIGSEGV时主栈溢出
真正棘手的是 SIGSEGV 和 SIGBUS —— 它们往往意味着内存越界或非法访问,此时首要任务是记录上下文并退出,而不是尝试“恢复”。强行处理只会掩盖 bug,让问题更难复现。











