windows用setconsolectrlhandler注册全局/静态处理器捕获ctrl+c,linux/macos用sigaction处理sigint,二者均不可用try/catch捕获,因信号与c++异常机制无关。

Windows下用SetConsoleCtrlHandler捕获Ctrl+C
Windows控制台默认把Ctrl+C当作中断信号直接终止进程,不走C++异常机制。想拦截它,必须用Windows API注册控制台控制处理器。
关键点: handler函数必须是全局或静态的,不能是类成员函数;注册后需保持主线程存活,否则handler可能收不到信号。
-
SetConsoleCtrlHandler返回TRUE才表示注册成功,务必检查 - handler里不能调用
std::cout、printf等可能触发锁的操作,容易死锁;改用WriteConsoleA或直接退出 - 只对当前控制台进程有效,子进程不会继承该handler
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) {
if (fdwCtrlType == CTRL_C_EVENT) {
// 安全操作:设标志位、写日志文件、清理资源
g_shutdown_requested = true;
return TRUE; // 表示已处理,不终止进程
}
return FALSE;
}
// 注册
SetConsoleCtrlHandler(CtrlHandler, TRUE);
Linux/macOS用signal或sigaction处理SIGINT
Unix-like系统用SIGINT对应Ctrl+C,但signal接口有移植性问题(比如某些系统会重置信号处理为默认),推荐用sigaction。
注意:信号处理函数中只能调用异步信号安全函数(如write、_exit),std::string、new、malloc、printf都**不安全**。
立即学习“C++免费学习笔记(深入)”;
-
sigaction的sa_flags建议加SA_RESTART,避免系统调用被中断后不自动重试 - 不要在handler里修改共享数据结构(如
std::vector),应仅设置volatile sig_atomic_t标志 - 多线程程序中,
SIGINT只发给主线程,其他线程无法直接捕获
volatile sig_atomic_t g_sigint_received = 0;
void sigint_handler(int) { g_sigint_received = 1; }
struct sigaction sa = {};
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGINT, &sa, nullptr);
为什么不能用try/catch捕获Ctrl+C
C++的try/catch只捕获throw抛出的异常,而Ctrl+C在底层触发的是操作系统信号(Windows的CTRL_C_EVENT / Unix的SIGINT),和C++异常机制完全无关。强行混用会导致未定义行为。
- 有些编译器(如MSVC)支持
/EHsc+set_terminate间接响应,但不可靠,且不跨平台 - 试图在catch块里处理Ctrl+C,实际永远不会进入——信号根本不会变成C++异常
- 第三方库(如Boost.ASIO)封装了信号处理,但底层仍是调用上述系统API,不是语法糖
跨平台封装要注意信号/事件的生命周期
如果写一个可移植的“优雅退出”模块,核心难点不在注册,而在确保handler能安全访问程序状态,且退出逻辑不被重复触发。
- 用
std::atomic<bool></bool>代替volatile sig_atomic_t更现代,但需确认目标平台ABI兼容(尤其嵌入式) - Windows下
SetConsoleCtrlHandler注册的handler,在进程退出前若再次调用会覆盖旧handler;Unix下sigaction同理 - 最易忽略的一点:handler执行期间,主循环可能正持有锁或处于析构中途——此时修改全局状态极易引发崩溃
真正安全的做法,是让handler只做最小动作(设原子标志),然后由主循环定期轮询并执行清理。别指望一次注册就搞定所有退出路径。










