Ctrl+C触发SIGINT信号,C++程序需用signal()或sigaction()拦截处理;推荐sigaction()因其更可靠、不自动重置且支持精细控制;信号处理函数中仅能调用异步信号安全函数。

Ctrl+C 会触发 SIGINT,用 signal() 或 sigaction() 注册处理函数
按下 Ctrl+C 默认终止进程,但 C++ 程序可通过系统信号机制拦截。Linux/macOS 下它发送 SIGINT(值为 2),Windows 控制台也支持(尽管信号语义略有不同)。关键不是“C++ 原生支持”,而是调用 POSIX/C 运行时接口——所以必须用 signal() 或更可靠的 sigaction(),不能靠 try/catch。
signal() 简单但有陷阱:重入、不可移植、自动重置
常见写法是 signal(SIGINT, handler),但要注意:
-
handler函数只能调用异步信号安全函数(如write()、_exit()),不能用cout、malloc、printf—— 否则行为未定义 - 某些系统(如旧版 glibc)在进入 handler 后会自动将信号处理重置为默认(
SIG_DFL),导致第二次 Ctrl+C 直接退出 - 不保证 handler 执行期间屏蔽同信号,可能被重复中断(虽 SIGINT 通常不会快速连按)
示例(仅限简单场景):
#include#include void sigint_handler(int) { write(1, "Caught SIGINT\n", 14); // 安全:用 write 而非 cout _exit(0); // 安全:不用 exit()(会调 cleanup) } int main() { signal(SIGINT, sigint_handler); while (true) {} // 等待信号 }
推荐用 sigaction():可屏蔽信号、不重置、可设标志
sigaction() 是 POSIX 标准方式,控制粒度更细:
立即学习“C++免费学习笔记(深入)”;
- 通过
sa_flags可设SA_RESTART(系统调用被中断后自动重试)、SA_NODEFER(handler 中不屏蔽本信号)等 - 用
sa_mask显式指定 handler 执行期间要屏蔽的其他信号(防竞态) - 注册后不会自动恢复为默认行为,避免意外退出
示例(更健壮):
#include#include void sigint_handler(int sig) { write(1, "SIGINT received\n", 16); _exit(0); } int main() { struct sigaction sa; sa.sa_handler = sigint_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; // 防 read()/sleep() 被打断后失败 sigaction(SIGINT, &sa, nullptr); pause(); // 等待信号(比 busy-loop 更省 CPU) }
在多线程程序中,信号只发给「某个线程」,需主动管理
POSIX 规定:信号递送给进程中任意一个未屏蔽该信号的线程。这带来两个实际问题:
- 主线程注册了 handler,但 SIGINT 可能被子线程接收 → handler 不执行
- 多个线程都未屏蔽 SIGINT,可能多个线程同时进入 handler(危险)
通用做法:在主线程中屏蔽所有信号(pthread_sigmask(SIG_BLOCK, &set, nullptr)),再用 sigwait() 在专用线程里同步等待;或用 signalfd()(Linux 特有)把信号转为文件描述符事件。不要依赖主线程天然收到信号。
真正难的不是注册 handler,而是确保 handler 里只做最简操作(比如设个 volatile sig_atomic_t flag),然后由主逻辑轮询退出;否则一不留神就踩进信号不安全函数的坑里。











