在C++程序运行过程中,操作系统会通过信号向进程通知特定事件,比如用户按下Ctrl+C会发送SIGINT信号,程序访问非法内存会发送SIGSEGV信号。signal函数是C标准库提供的信号处理接口,在C++中也可以直接使用,用来注册信号对应的处理逻辑。

signal函数的基本定义与用法
signal函数声明在<csignal>头文件中,函数原型为:
#include <csignal> // 参数signum是要处理的信号编号,handler是信号处理函数 // 返回值是之前注册的信号处理函数指针,失败返回SIG_ERR void (*signal(int signum, void (*handler)(int)))(int);
常见的信号编号包括:
- SIGINT:中断信号,通常由Ctrl+C触发
- SIGTERM:终止信号,是kill命令默认发送的信号
- SIGSEGV:段错误信号,访问非法内存时触发
- SIGABRT:终止信号,调用abort函数时触发
使用signal函数捕获信号的基础示例
下面的代码演示了如何注册SIGINT信号的处理函数,当用户按下Ctrl+C时,程序不会直接退出,而是执行自定义的逻辑:
#include <iostream>
#include <csignal>
#include <unistd.h>
// 自定义信号处理函数,参数signum是触发的信号编号
void signal_handler(int signum) {
std::cout << "捕获到信号:" << signum << ",程序即将退出" << std::endl;
// 可以在这里做资源清理工作
exit(signum);
}
int main() {
// 注册SIGINT信号的处理函数
if (signal(SIGINT, signal_handler) == SIG_ERR) {
std::cout << "注册信号处理函数失败" << std::endl;
return 1;
}
std::cout << "程序运行中,按下Ctrl+C触发SIGINT信号" << std::endl;
// 循环等待信号触发
while (true) {
sleep(1);
}
return 0;
}
信号处理与C++异常的区别
很多开发者会混淆信号和C++异常,两者有本质区别:
| 对比项 | 信号 | C++异常 |
|---|---|---|
| 触发来源 | 操作系统、外部事件、硬件错误 | 程序内部通过throw语句主动抛出 |
| 处理机制 | 通过signal注册全局处理函数,是异步触发的 | 通过try-catch块捕获,是同步触发的 |
| 作用范围 | 面向整个进程 | 面向当前调用栈的作用域 |
| 是否可恢复 | 部分信号可恢复,部分会导致进程直接退出 | 捕获后可以恢复程序执行 |
需要注意的是,信号处理函数中不能抛出C++异常,因为信号处理是异步的,抛出异常的栈上下文可能不符合C++异常的处理要求,会导致未定义行为。
signal函数捕获信号的进阶注意事项
信号处理函数的可重入性
信号处理函数应该是可重入的,因为信号可能在任意时刻触发,包括正在执行malloc、printf等非可重入函数的时候。因此信号处理函数中尽量只使用简单的赋值操作,避免使用标准库的非可重入函数。
信号的阻塞与恢复
如果需要临时屏蔽某些信号,可以使用sigprocmask函数,避免信号处理函数执行时被同类信号打断:
#include <iostream>
#include <csignal>
#include <unistd.h>
void handler(int signum) {
std::cout << "处理信号:" << signum << std::endl;
}
int main() {
// 注册SIGTERM信号的处理函数
signal(SIGTERM, handler);
sigset_t mask, old_mask;
// 初始化信号集,添加SIGTERM信号
sigemptyset(&mask);
sigaddset(&mask, SIGTERM);
// 阻塞SIGTERM信号,保存原来的信号掩码
sigprocmask(SIG_BLOCK, &mask, &old_mask);
std::cout << "信号已阻塞,等待3秒" << std::endl;
sleep(3);
// 恢复原来的信号掩码,解除SIGTERM的阻塞
sigprocmask(SIG_SETMASK, &old_mask, nullptr);
std::cout << "信号已恢复,等待信号触发" << std::endl;
sleep(10);
return 0;
}
不同平台下的兼容性
signal函数的行为在不同操作系统下存在差异,比如BSD系统和System V系统的signal函数在信号处理后是否会自动恢复默认处理行为不同。如果需要跨平台兼容,建议使用sigaction函数替代signal函数,sigaction可以明确指定信号处理的行为,避免兼容性问题。
常见错误与解决方法
- 错误:在信号处理函数中调用exit之外的复杂逻辑,比如操作复杂数据结构。解决方法:信号处理函数只做简单的标记设置,主程序定期检查标记再处理复杂逻辑。
- 错误:忽略信号的默认行为,比如SIGSEGV信号默认会终止进程并产生核心转储,如果自定义处理函数没有正确退出,可能导致程序进入死循环。
- 错误:在多线程程序中使用signal函数,signal是进程级别的信号处理,多线程下建议使用pthread_sigmask管理线程的信号掩码。
合理使用signal函数可以让C++程序更稳健地处理外部事件和系统异常,避免程序意外退出,同时做好信号处理中的边界情况处理,能进一步提升程序的可靠性。