Ctrl+C默认触发SIGINT,需用signal()或sigaction()注册处理函数;handler中仅能操作volatile sig_atomic_t变量,禁用非异步信号安全函数;Windows下应使用SetConsoleCtrlHandler()。

Ctrl+C 默认触发 SIGINT,用 signal() 注册处理函数
按下 Ctrl+C 时,终端会向当前进程发送 SIGINT 信号。C++ 本身不提供信号机制,需调用 C 标准库的 signal() 函数注册处理逻辑。注意:这不是线程安全的,且仅适用于简单场景(如优雅退出)。
常见错误是把处理函数写成带参数但返回值不是 void,或忽略 signal() 的返回值检查——失败时它返回 SIG_ERR,不检查容易埋下静默失败隐患。
-
signal(SIGINT, handler)中的handler必须是签名形如void handler(int)的函数指针 - 注册后,首次触发
SIGINT会调用该函数;某些系统(如 Linux)会自动重置为默认行为,需在 handler 结尾再次调用signal(SIGINT, handler)才能持续捕获 - 不要在 handler 里调用
std::cout、malloc、new等非异步信号安全函数——可能引发未定义行为
用 sigaction() 替代 signal() 更可靠
signal() 行为在不同系统上不一致(比如是否自动重置、是否阻塞同类信号),POSIX 推荐用 sigaction()。它支持精确控制信号掩码、是否重启被中断的系统调用等。
关键点在于:必须显式设置 sa_handler、清空 sa_mask、指定 SA_RESTART 或 SA_RESETHAND 等标志位,否则可能收不到第二次信号或阻塞其他信号。
立即学习“C++免费学习笔记(深入)”;
- 声明
struct sigaction sa;后,先用memset(&sa, 0, sizeof(sa))清零,避免未初始化字段导致意外行为 - 设置
sa.sa_handler = handler;,并调用sigemptyset(&sa.sa_mask)防止阻塞其他信号 - 若希望系统调用(如
read())被中断后自动重试,加sa.sa_flags |= SA_RESTART;若想 handler 执行完后恢复默认行为,加SA_RESETHAND
全局 volatile 变量用于跨信号上下文通信
信号处理函数不能安全调用大多数标准库函数,所以常用一个 volatile sig_atomic_t 类型的全局变量做“标志位”,主循环定期检查它。
别用普通 int 或 bool——编译器可能优化掉读取,或因非原子操作导致值错乱。也别在 handler 里直接修改复杂对象或调用 exit()(虽然它信号安全,但会跳过栈展开,RAII 资源不释放)。
- 声明:
volatile sig_atomic_t g_sigint_received = 0; - handler 内只做:
g_sigint_received = 1; - 主循环中:
while (!g_sigint_received) { /* do work */ },退出前做清理
Windows 下需用 SetConsoleCtrlHandler
Windows 没有 SIGINT 概念,Ctrl+C 触发的是控制台控制事件。必须用 Windows API SetConsoleCtrlHandler() 注册回调,且只对控制台程序有效。
该函数注册的 handler 运行在特殊线程上,同样禁止调用大部分 Win32 API(如 CreateThread、MessageBox),也不能执行耗时操作——系统可能强制终止进程。
- handler 原型是
BOOL WINAPI HandlerRoutine(DWORD dwCtrlType),需判断dwCtrlType == CTRL_C_EVENT - 返回
TRUE表示已处理,系统不再调用默认处理(即不退出);返回FALSE则继续默认流程 - 跨平台代码通常用
#ifdef _WIN32分支分别处理
信号处理真正难的不是注册函数,而是确保 handler 里只做最简操作,并让主逻辑及时响应这个“中断请求”。很多崩溃都源于在 handler 中调用了看似无害的 printf 或修改了非原子变量。








