不能可靠拦截 Ctrl+C,因 signal() 非线程安全、行为不一致、多次触发易崩溃,且被库调用干扰;应使用 sigaction() 原子注册并配合 volatile sig_atomic_t 标志位延迟清理。

signal() 能否可靠拦截 Ctrl+C
signal() 可以注册对 SIGINT 的处理函数,从而响应 Ctrl+C,但**它不是线程安全的,且行为在不同系统上不一致(尤其是信号被多次触发时)**。POSIX 明确指出 signal() 是为兼容旧代码保留的接口,sigaction() 才是推荐方案。
常见错误现象:
- 注册后按 Ctrl+C 无反应(可能被后续库调用覆盖)
- 多次 Ctrl+C 导致程序崩溃(signal() 默认重置为 SIG_DFL)
- 在多线程环境中信号被任意线程接收,无法控制
- 使用
signal(SIGINT, handler)后,一旦触发一次,部分系统会自动恢复为默认行为,需手动再次调用signal()(不推荐) - 若程序中用了
std::thread或第三方库(如 Boost、Qt),它们可能内部调用sigprocmask或pthread_sigmask,干扰signal()效果 -
SIGINT是唯一保证由终端发送给前台进程组的信号,但仅当进程未忽略或阻塞它时才生效
用 sigaction() 正确注册 SIGINT 处理器
sigaction() 提供原子性注册、可屏蔽其他信号、支持重启被中断的系统调用等能力,是 C++ 中拦截 Ctrl+C 的实际标准做法。
关键点:
- 必须显式设置 sa_flags(例如 SA_RESTART 避免 read() 等调用被中断)
- sa_mask 可临时阻塞其他信号,防止嵌套调用(如 SIGINT 处理中又收到 SIGUSR1)
- 不要从信号处理器中调用非异步信号安全函数(如 std::cout、malloc、printf)
struct sigaction sa;
sa.sa_handler = [](int) {
// 仅允许异步信号安全操作:写入全局 volatile 变量、_exit()、siglongjmp()
g_should_exit = true; // 声明为 volatile sig_atomic_t
};
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; // 让阻塞系统调用自动重试
sigaction(SIGINT, &sa, nullptr);
volatile sig_atomic_t 是什么,为什么不能用 bool
信号处理器可能在任意指令中间打断主流程,普通 bool 变量的读写不是原子的,编译器还可能对其做优化(比如缓存到寄存器),导致主循环永远看不到变化。
-
sig_atomic_t是 C/C++ 标准定义的、能被信号处理器安全读写的整数类型(通常为int) - 必须配合
volatile使用,禁止编译器优化掉重复读取 - 声明示例:
volatile sig_atomic_t g_should_exit = 0; - 主循环中应这样检查:
while (!g_should_exit) { ... },而非while (true)+ 内部 break
退出前清理资源要注意什么
信号处理器里不能调用 delete、close()、fclose()、std::mutex::unlock() 等——这些都不是异步信号安全函数。
立即学习“C++免费学习笔记(深入)”;
正确做法是「延迟处理」:
- 信号处理器只设标志位
- 主线程检测到标志后,按常规逻辑执行清理(关闭文件、释放内存、join 线程等)
- 若使用 std::thread,确保所有线程已 join 或 detach,否则 main() 返回会终止整个进程,未 join 的线程被强制销毁(C++11 起会调用 std::terminate)
容易被忽略的一点:某些系统调用(如 epoll_wait、select)在被 SIGINT 中断后返回 -1 并设 errno = EINTR,需主动重试或退出,否则程序可能卡住不动。










