signal()函数通过传入信号编号(如SIGINT)和C风格函数指针注册处理函数,要求处理函数为全局或static,仅能调用异步信号安全函数,且跨平台行为不一致,故推荐使用更可靠的sigaction()。

signal() 函数怎么注册信号处理函数
用 signal() 注册处理函数最直接,但只适合简单场景。它接受两个参数:信号编号(如 SIGINT)和函数指针。注意该函数指针必须是 C 风格的,不能带捕获的 lambda,也不能是成员函数——否则编译报错 invalid conversion from ‘void (A::*)()’ to ‘__sighandler_t’。
常见写法:
#include <csignal>
#include <iostream>
void handle_sigint(int sig) {
std::cout << "Caught SIGINT\n";
// 不要在信号处理函数里调用 cout / malloc / printf 等异步信号不安全函数
}
int main() {
signal(SIGINT, handle_sigint); // Ctrl+C 触发
while(true) {}
}
- 必须用全局函数或 static 函数,不能是普通成员函数
- 处理函数里只能调用异步信号安全函数(
write()、_exit()、siglongjmp()等),std::cout和printf都不安全 -
signal()在不同系统上语义不一致(比如 Linux 默认重置 handler,BSD 保持原状),可移植性差
sigaction() 为什么比 signal() 更可靠
sigaction() 是 POSIX 标准推荐方式,能精确控制信号行为,避免 signal() 的模糊语义。它用 struct sigaction 封装所有选项,比如是否自动阻塞同类型信号、是否重启被中断的系统调用。
关键点:
立即学习“C++免费学习笔记(深入)”;
- 设置
sa_flags |= SA_RESTART可让被信号中断的read()、accept()自动重试,否则可能返回 -1 并设errno = EINTR - 设置
sa_mask可临时屏蔽其他信号(比如在处理SIGUSR1时不想被SIGUSR2打断) - 必须显式初始化整个结构体(用
memset(&sa, 0, sizeof(sa))或{0}),否则未赋值字段是随机值,可能导致崩溃
示例:
struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_sigint; sa.sa_flags = SA_RESTART; sigaction(SIGINT, &sa, nullptr);
如何安全地从信号处理函数通知主逻辑
信号处理函数不能安全调用大部分 C++ 标准库,也不能操作复杂对象。真正可行的做法是用 volatile sig_atomic_t 标志位 + 主循环轮询,或者用 signalfd()(Linux 特有)把信号转成文件描述符读取。
-
volatile sig_atomic_t flag = 0;是唯一被标准保证线程/信号安全的整型类型,其他类型(包括bool、int)都可能因编译器优化出问题 - 不要在 handler 里修改
std::string、调用std::vector::push_back()或锁 mutex —— 这些都不是异步信号安全的 - 如果需要更复杂的通信(比如传递数据),应改用
self-pipe trick:信号 handler 中write()到一个 pipe,主循环select()或epoll_wait()监听该 fd
常见崩溃原因和调试线索
信号处理出问题往往表现为段错误、死锁或静默失败,而不是明显报错。典型现象包括:
- 程序收到信号后立即 crash,可能是 handler 里调用了
std::cout或free()—— 查gdbbacktrace,看栈顶是不是在malloc_consolidate或__libc_signal_handler -
SIGSEGV被反复触发停不下来,说明 handler 本身又触发了 segfault(比如解引用空指针),此时需用sigprocmask()在 handler 开头屏蔽自身信号 - Ctrl+C 没反应,检查是否被子进程继承了忽略状态(
signal(SIGINT, SIG_IGN)后 fork 的子进程也会忽略),或是否被system()、popen()临时改写了 handler
真正难的不是注册 handler,而是确保 handler 内部不越界、不调用非安全函数、不依赖主程序当前状态。哪怕只改一个 volatile sig_atomic_t,也要确认编译器没把它优化掉(加 volatile 且不放在寄存器里)。


















