用 signal() 捕获 Ctrl+C 需注册异步信号安全的处理函数(如仅设 volatile sig_atomic_t 标志),避免调用非安全函数;推荐改用更可靠、可移植的 sigaction(),Windows 下应优先使用 SetConsoleCtrlHandler()。

如何用 signal() 捕获 Ctrl+C(SIGINT)
Ctrl+C 默认会终止进程,但 C++ 程序可以通过 signal() 注册自定义处理函数来拦截它。关键不是“阻止退出”,而是“在退出前做清理”。signal() 是 POSIX 标准函数,在 Windows(MSVC/MinGW)和 Linux/macOS 都可用,但行为细节有差异。
常见错误是把处理函数写成带复杂逻辑(比如调用 std::cout、new 或锁)的普通函数——这属于未定义行为。POSIX 只允许在信号处理函数中调用异步信号安全函数(如 write()、_exit()),std::cout 和大多数 STL 函数都不在此列。
实操建议:
- 声明处理函数为
void handler(int),用extern "C"避免 C++ 名字修饰(尤其在 Windows 上) - 注册时用
signal(SIGINT, handler);返回值为SIG_ERR表示失败(比如信号被屏蔽或不可捕获) - 处理函数内只做最简操作:设置一个全局
volatile sig_atomic_t标志位,或用write()输出提示后调用_exit() - 不要在 handler 里调用
printf、std::cout、malloc、std::string::append等非异步信号安全函数
为什么推荐用 sigaction() 替代 signal()
signal() 在不同系统上语义不一致:Linux 默认重置信号处理为 SIG_DFL(即第二次 Ctrl+C 会直接退出),而 macOS 可能保持原 handler;且无法屏蔽其他信号或控制重启系统调用。sigaction() 是更可靠、可移植的选择。
立即学习“C++免费学习笔记(深入)”;
使用 sigaction() 的要点:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 声明
struct sigaction sa,用memset(&sa, 0, sizeof(sa))清零 - 设置
sa.sa_handler = handler,并用sigemptyset(&sa.sa_mask)显式清空阻塞集(避免意外阻塞) - 关键:设
sa.sa_flags = SA_RESTART(让被中断的系统调用自动重试,比如read()不会因 SIGINT 返回 -1+EINTR) - 调用
sigaction(SIGINT, &sa, nullptr);失败时检查errno(如EINVAL表示非法信号)
如何安全地在主循环中响应中断
信号是异步的,不能指望 handler 做完所有事。典型模式是:handler 只修改一个 volatile sig_atomic_t 全局变量(如 g_stop_requested),主逻辑定期检查它并优雅退出。
示例场景(简单服务器循环):
volatile sig_atomic_t g_stop_requested = 0;
void signal_handler(int) {
g_stop_requested = 1;
}
// 主线程
while (!g_stop_requested) {
// 处理网络请求、读文件等
if (some_io_operation() == -1 && errno == EINTR) continue; // 被信号中断,重试
}
// 这里执行 close(fd)、delete resources 等清理
注意:sig_atomic_t 是唯一保证原子读写的整型类型;普通 int 或 bool 在多线程+信号下可能被撕裂。Windows 上需额外包含 <csignal> 并确认 CRT 支持(MSVC 默认支持)。
Windows 控制台特殊处理:SetConsoleCtrlHandler()
Windows 不完全遵循 POSIX 信号模型,signal(SIGINT, ...) 在某些终端(如 PowerShell、新 Windows Terminal)下可能失效。更可靠的方式是使用 Win32 API SetConsoleCtrlHandler()。
使用要点:
- 函数签名必须是
BOOL WINAPI handler(DWORD dwCtrlType),WINAPI表示__stdcall调用约定 -
dwCtrlType可能是CTRL_C_EVENT、CTRL_BREAK_EVENT等,返回TRUE表示已处理,不再触发默认行为 - 同样禁止在 handler 中调用
std::cout、CRT 文件函数;可用WriteConsoleA()安全输出 - 需在主线程调用一次
SetConsoleCtrlHandler(handler, TRUE);返回FALSE表示失败(如权限不足)
混合方案常见:先调用 SetConsoleCtrlHandler()(Windows),再 fallback 到 signal() 或 sigaction()(跨平台编译时)。真正麻烦的是跨平台信号语义差异——比如 Ctrl+Break 在 Windows 是独立事件,Linux 没有对应物。

















