C++信号处理

C++ 信号处理


一、信号处理 基础概念

1.1 什么是信号

信号(Signal)是 操作系统向进程发送的异步软中断

作用:通知进程发生了异常、外部中断、终止请求等事件。

特点:

  • 异步触发,随时可能打断程序正常执行流

  • 由内核、终端、其他进程、程序自身产生

  • C++ 兼容 C 标准信号机制,头文件:

    cpp 复制代码
    #include <csignal>

1.2 信号常见触发来源

  1. 终端按键:Ctrl + CCtrl + \
  2. 程序运行异常:除零错误、非法内存访问、非法指令
  3. 系统内核:进程被杀、超时、管道断开
  4. 代码主动调用:raise()abort()
  5. 其他进程发送信号(Linux kill 命令)

1.3 信号处理核心流程

  1. 定义信号回调处理函数
  2. 使用 signal() 注册信号与回调
  3. 信号到来时,系统自动中断程序,执行回调
  4. 回调结束后,默认恢复程序执行或退出

二、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 系统内置处理方式

无需自定义函数,直接使用系统宏:

  1. SIG_DFL默认处理
    触发后系统默认行为:终止程序、产生核心转储等
  2. 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;
}

六、信号处理函数 严格限制(工程必看)

信号回调函数不能随意写代码,有严格安全限制:

  1. 禁止调用非可重入函数:coutprintfmalloc、文件IO 等(尽量少用)
  2. 禁止复杂业务逻辑、循环、耗时操作
  3. 不要在回调中嵌套信号注册
  4. 建议只做:标记全局状态、简单日志、设置退出标志
  5. 信号是异步的,极易引发竞态条件,多线程环境慎用

推荐最佳实践:

信号回调只修改一个全局标记变量,主循环检测标记再做优雅退出和资源释放。


七、跨平台兼容差异

Linux / macOS

  • 信号机制完整,支持全部标准信号
  • sleep(秒)<unistd.h>
  • 支持 kill 命令发信号
  • 段错误可生成 core 文件用于崩溃分析

Windows

  • 仅支持子集常用信号:SIGINTSIGABRTSIGFPESIGILLSIGSEGV
  • 睡眠函数:Sleep(毫秒),头文件 <windows.h>
  • 不支持 Unix 大量扩展信号
  • 无 core 转储机制

八、工程实际应用场景

  1. 后台服务程序 :捕获 Ctrl+C 优雅退出,关闭socket、保存配置、释放资源
  2. 崩溃防护:捕获段错误、算术异常,记录崩溃现场日志
  3. 守护进程:监听终止信号,做重启或善后处理
  4. 调试定位:捕获异常信号,打印行号、文件信息
  5. 定时任务 :利用 SIGALRM 实现简单定时器

九、核心知识点总结

  1. 信号是操作系统发给进程的异步软中断
  2. signal() 注册信号回调,raise() 主动发信号;
  3. 可使用 SIG_DFL 默认处理、SIG_IGN 忽略信号;
  4. 常用信号:SIGINTSIGSEGVSIGFPESIGABRTSIGTERM
  5. 信号回调必须精简、可重入,禁止复杂逻辑;
  6. 适合做优雅退出、崩溃捕获、资源善后、服务守护;
  7. Linux 与 Windows 信号子集有差异,跨平台需兼容适配。
相关推荐
Legendary_0083 小时前
LDR6500:USB‑C DRP PD协议芯片技术详解与应用实践
c语言·开发语言
2301_800976933 小时前
正则表达式
开发语言·python·正则表达式
故事还在继续吗3 小时前
C++20关键特性
开发语言·c++·c++20
青少儿编程课堂4 小时前
2026青少儿信息素养大赛备赛指南!Python/Scratch/C++备考要点
开发语言·c++·python
旖-旎4 小时前
深搜练习(电话号码字母组合)(3)
c++·算法·力扣·深度优先遍历
AIFarmer4 小时前
【无标题】
开发语言·c++·算法
昇腾CANN4 小时前
TileLang-Ascend 算子性能优化方法与实操
开发语言·javascript·性能优化·昇腾·cann
John_ToDebug5 小时前
WebHostView 与 TabStrip 交互机制深度解析
c++·chrome·windows