C++ 信号处理
一、信号处理 基础概念
1.1 什么是信号
信号(Signal)是 操作系统向进程发送的异步软中断 。
作用:通知进程发生了异常、外部中断、终止请求等事件。
特点:
-
异步触发,随时可能打断程序正常执行流
-
由内核、终端、其他进程、程序自身产生
-
C++ 兼容 C 标准信号机制,头文件:
cpp#include <csignal>
1.2 信号常见触发来源
- 终端按键:
Ctrl + C、Ctrl + \ - 程序运行异常:除零错误、非法内存访问、非法指令
- 系统内核:进程被杀、超时、管道断开
- 代码主动调用:
raise()、abort() - 其他进程发送信号(Linux
kill命令)
1.3 信号处理核心流程
- 定义信号回调处理函数
- 使用
signal()注册信号与回调 - 信号到来时,系统自动中断程序,执行回调
- 回调结束后,默认恢复程序执行或退出
二、C++ 标准预定义信号列表
所有信号均定义在 <csignal> 头文件中:
| 信号常量 | 值 | 含义 | 触发场景 |
|---|---|---|---|
SIGINT |
2 | 终端中断信号 | Ctrl + C |
SIGABRT |
6 | 程序异常终止 | 调用 abort() |
SIGFPE |
8 | 浮点算术异常 | 除零、数值溢出 |
SIGILL |
4 | 非法硬件指令 | 执行无效机器指令 |
SIGSEGV |
11 | 段错误 | 非法内存访问、野指针 |
SIGTERM |
15 | 软件终止请求 | 系统/其他进程请求结束程序 |
SIGQUIT |
3 | 终端退出信号 | Ctrl + \ |
SIGALRM |
14 | 闹钟超时信号 | 定时器到时 |
SIGBUS |
7 | 总线错误 | 内存地址对齐错误 |
注意:部分信号不能被捕获或忽略 ,如 Linux
SIGKILL。
三、signal() 信号注册函数详解
3.1 函数原型
cpp
void (*signal(int sig, void (*handler)(int)))(int);
拆解说明:
- 第一个参数
sig:要监听的信号编号 - 第二个参数
handler:信号处理函数指针 - 返回值:返回旧的处理函数指针
3.2 信号处理函数规范
固定格式,必须严格遵守:
cpp
void 回调函数名(int signum)
{
// signum:当前触发的信号值
// 信号处理逻辑
}
3.3 系统内置处理方式
无需自定义函数,直接使用系统宏:
SIG_DFL:默认处理
触发后系统默认行为:终止程序、产生核心转储等SIG_IGN:忽略该信号
收到信号不做任何处理,程序继续运行
使用示例:
cpp
// 忽略 Ctrl+C
signal(SIGINT, SIG_IGN);
// 恢复系统默认处理
signal(SIGINT, SIG_DFL);
四、raise() 主动发送信号
4.1 函数原型
cpp
int raise(int sig);
- 功能:进程内部主动向自身发送一个指定信号
- 返回值:成功返回 0,失败返回非0
- 常用于模拟异常、测试信号回调逻辑
4.2 abort() 主动异常终止
cpp
void abort(void);
- 内部会触发
SIGABRT信号 - 程序异常终止,可生成 core 崩溃日志
五、完整实战案例
案例1:捕获 Ctrl+C 优雅退出
cpp
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void sigHandler(int sig)
{
cout << "\n======================" << endl;
cout << " 捕获到信号:" << sig << " (Ctrl+C)" << endl;
cout << " 开始释放资源、保存数据..." << endl;
cout << " 程序安全退出" << endl;
cout << "======================" << endl;
// 收尾后退出
exit(EXIT_SUCCESS);
}
int main()
{
// 注册 SIGINT 信号回调
signal(SIGINT, sigHandler);
cout << "程序已启动,按 Ctrl+C 触发退出" << endl;
while (true)
{
cout << "程序正常运行中..." << endl;
sleep(1);
}
return 0;
}
案例2:忽略信号 & 恢复默认处理
cpp
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
int main()
{
// 忽略 Ctrl+C
signal(SIGINT, SIG_IGN);
cout << "已忽略 Ctrl+C,按 Ctrl+C 无效" << endl;
sleep(5);
// 恢复系统默认处理
signal(SIGINT, SIG_DFL);
cout << "5秒后恢复默认,现在 Ctrl+C 可终止程序" << endl;
while(true)
sleep(1);
return 0;
}
案例3:程序主动 raise 触发信号
cpp
#include <iostream>
#include <csignal>
using namespace std;
void callback(int sig)
{
cout << "回调触发,信号编号:" << sig << endl;
}
int main()
{
signal(SIGINT, callback);
cout << "程序主动发送 SIGINT 信号" << endl;
// 主动给自己发信号
raise(SIGINT);
cout << "信号处理完毕,继续执行后续代码" << endl;
return 0;
}
案例4:同时注册捕获多个信号
cpp
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void multiSigHandler(int sig)
{
switch(sig)
{
case SIGINT:
cout << "\n收到 Ctrl+C 中断" << endl;
break;
case SIGSEGV:
cout << "\n捕获段错误,非法内存访问" << endl;
exit(1);
break;
case SIGFPE:
cout << "\n捕获算术异常(除零/溢出)" << endl;
exit(1);
break;
default:
cout << "\n收到未知信号:" << sig << endl;
}
}
int main()
{
// 一次性注册多个信号共用同一个处理函数
signal(SIGINT, multiSigHandler);
signal(SIGSEGV, multiSigHandler);
signal(SIGFPE, multiSigHandler);
while(true)
{
cout << "多信号监听中..." << endl;
sleep(2);
}
return 0;
}
六、信号处理函数 严格限制(工程必看)
信号回调函数不能随意写代码,有严格安全限制:
- 禁止调用非可重入函数:
cout、printf、malloc、文件IO 等(尽量少用) - 禁止复杂业务逻辑、循环、耗时操作
- 不要在回调中嵌套信号注册
- 建议只做:标记全局状态、简单日志、设置退出标志
- 信号是异步的,极易引发竞态条件,多线程环境慎用
推荐最佳实践:
信号回调只修改一个全局标记变量,主循环检测标记再做优雅退出和资源释放。
七、跨平台兼容差异
Linux / macOS
- 信号机制完整,支持全部标准信号
sleep(秒):<unistd.h>- 支持
kill命令发信号 - 段错误可生成 core 文件用于崩溃分析
Windows
- 仅支持子集常用信号:
SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV - 睡眠函数:
Sleep(毫秒),头文件<windows.h> - 不支持 Unix 大量扩展信号
- 无 core 转储机制
八、工程实际应用场景
- 后台服务程序 :捕获
Ctrl+C优雅退出,关闭socket、保存配置、释放资源 - 崩溃防护:捕获段错误、算术异常,记录崩溃现场日志
- 守护进程:监听终止信号,做重启或善后处理
- 调试定位:捕获异常信号,打印行号、文件信息
- 定时任务 :利用
SIGALRM实现简单定时器
九、核心知识点总结
- 信号是操作系统发给进程的异步软中断;
signal()注册信号回调,raise()主动发信号;- 可使用
SIG_DFL默认处理、SIG_IGN忽略信号; - 常用信号:
SIGINT、SIGSEGV、SIGFPE、SIGABRT、SIGTERM; - 信号回调必须精简、可重入,禁止复杂逻辑;
- 适合做优雅退出、崩溃捕获、资源善后、服务守护;
- Linux 与 Windows 信号子集有差异,跨平台需兼容适配。