17370845950

C++怎么捕获Ctrl+C信号 C++ signal信号处理函数用法【系统】
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::coutmallocnew 等非异步信号安全函数——可能引发未定义行为

用 sigaction() 替代 signal() 更可靠

signal() 行为在不同系统上不一致(比如是否自动重置、是否阻塞同类信号),POSIX 推荐用 sigaction()。它支持精确控制信号掩码、是否重启被中断的系统调用等。

关键点在于:必须显式设置 sa_handler、清空 sa_mask、指定 SA_RESTARTSA_RESETHAND 等标志位,否则可能收不到第二次信号或阻塞其他信号。

  • 声明 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 类型的全局变量做“标志位”,主循环定期检查它。

别用普通 intbool——编译器可能优化掉读取,或因非原子操作导致值错乱。也别在 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(如 CreateThreadMessageBox),也不能执行耗时操作——系统可能强制终止进程。

  • handler 原型是 BOOL WINAPI HandlerRoutine(DWORD dwCtrlType),需判断 dwCtrlType == CTRL_C_EVENT
  • 返回 TRUE 表示已处理,系统不再调用默认处理(即不退出);返回 FALSE 则继续默认流程
  • 跨平台代码通常用 #ifdef _WIN32 分支分别处理

信号处理真正难的不是注册函数,而是确保 handler 里只做最简操作,并让主逻辑及时响应这个“中断请求”。很多崩溃都源于在 handler 中调用了看似无害的 printf 或修改了非原子变量。