signal()的第二个参数必须是void(int)签名的自由函数指针;正确做法是定义独立自由函数或static成员函数;共享状态只能用volatile sig_atomic_t;成员函数需通过static桥接;返回值为原handler函数指针。

signal() 的第二个参数必须是函数指针
你传给 signal() 的处理函数,本质上就是一个函数指针。C++ 不允许直接传 lambda(捕获变量的)、成员函数或普通变量——它只接受符合 void(int) 签名的自由函数地址。
常见错误是写成 signal(SIGINT, [](int){});,编译器会报错:无法将 lambda 转换为函数指针(除非是无捕获 lambda,但即便如此,signal() 也不保证可移植性)。
- ✅ 正确做法:定义独立的自由函数,如
void sigint_handler(int) - ✅ 可用
static成员函数(无 this 指针),但不能带对象状态 - ❌ 避免在 handler 中调用
std::cout、malloc、new等非异步信号安全函数 - ⚠️ 注意:
signal()在不同系统上语义不一致(比如是否自动重置 handler),生产环境推荐用sigaction()
volatile sig_atomic_t 是唯一安全的共享变量类型
信号可能在任意时刻中断主线程,所以 handler 和主逻辑之间共享的状态必须满足「异步信号安全」。普通 int 或 bool 变量读写可能被中断导致撕裂(torn write),而 sig_atomic_t 是标准保证原子读写的最小整型。
典型用法是用它做退出标志:
volatile sig_atomic_t g_shutdown = 0;
<p>void sigterm_handler(int) {
g_shutdown = 1;
}</p><p>int main() {
signal(SIGTERM, sigterm_handler);
while (!g_shutdown) {
// 工作循环
}
}
</p>
- ✅ 必须加
volatile:防止编译器优化掉重复读取 - ✅ 只能用于整型(
sig_atomic_t通常是int或long) - ❌ 不要试图用它传递复杂结构、指针或字符串
- ⚠️ 即使是
sig_atomic_t,也不能在 handler 中修改多个字段来模拟状态机——仍需单次原子赋值
成员函数不能直接当 signal handler,但可以间接桥接
类的非静态成员函数隐含 this 参数,签名不是 void(int),所以不能直接传给 signal()。强行转换会导致未定义行为。
可行的桥接方式只有两种:
- ✅ 使用
static成员函数 + 全局或静态指针指向对象实例(注意线程安全) - ✅ 将对象指针存入全局
sig_atomic_t可寻址位置(如static MyClass* g_instance),再在 static handler 中调用其方法 - ❌ 不要用
std::function或lambda绑定对象——它们内部依赖堆分配和异常机制,信号上下文里不可用 - ⚠️ 所有被调用的成员函数也必须遵守异步信号安全规则(不能用 STL、不能抛异常、不能锁 mutex)
signal() 返回值本身就是一个函数指针
signal() 的返回值类型是 void (*)(int),即「指向接收一个 int 参数、返回 void 的函数的指针」。你可以用它保存旧 handler,之后恢复:
void old_handler(int) { /* ... */ }
void new_handler(int) { /* ... */ }
<p>auto prev = signal(SIGINT, new_handler); // prev 是函数指针
// ...
signal(SIGINT, prev); // 恢复旧 handler
</p>
- ✅ 这种模式适合临时覆盖信号行为(比如调试时拦截 SIGSEGV)
- ✅ 返回值可用于判断原 handler 是否是
SIG_DFL或SIG_IGN - ❌ 不要试图对返回值做算术运算或 reinterpret_cast —— 它只是函数地址
- ⚠️ 多次调用
signal()可能因平台差异导致 handler 被重置,sigaction()更可靠
信号处理里指针不是炫技工具,而是约束下的生存策略:函数指针限定入口,volatile sig_atomic_t 限定状态,所有其他指针操作都得让位于异步安全这个硬边界。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











