在 C 程序运行过程中,像 Ctrl+C 中断、非法内存访问、浮点异常这类事件,往往不是普通函数调用流程的一部分,而是由系统在运行时“插进来”的异步通知。<signal.h> 提供的就是这套最基础的信号处理机制:你可以先理解信号从哪里来、常见类型分别表示什么,再看 signal() 如何把特定信号绑定到处理函数,最后结合限制条件判断它适合做什么、不适合做什么。
什么是信号,它从哪里来
信号可以理解为操作系统发给进程的一种异步通知,用来告诉程序“发生了一个需要立即注意的事件”。这类事件可能来自用户操作、程序本身的异常行为,或者其他进程的干预。
常见信号来源
- 操作系统:当进程执行异常操作时,系统会发送对应信号,例如非法内存访问可能触发相关错误信号。
- 用户:在终端中按下
Ctrl+C,通常会向前台程序发送SIGINT。 - 其他进程:进程之间也可以发送信号,用于简单通知或控制。
信号通常用来解决什么问题
- 错误处理:在程序运行出现问题时进行捕获和收尾。
- 异常处理:对某些异常事件做出响应,避免程序直接无提示退出。
- 进程间通信:用于较轻量的进程通知。
C 语言里常见的信号类型
在 <signal.h> 中,标准库预定义了一组常见信号宏。它们本质上是不同事件的标识符,程序可以根据不同信号做不同处理。

SIGABRT:程序调用abort()函数时发送。SIGFPE:浮点运算异常。SIGILL:非法指令。SIGINT:中断信号,通常由用户通过键盘触发。SIGSEGV:段错误,尝试访问未分配给进程的内存区域。SIGTERM:请求终止进程的信号。
如果你刚接触信号机制,可以先把这些类型分成两类来理解:一类是“用户或进程请求程序停下来”,例如 SIGINT、SIGTERM;另一类是“程序运行出现异常”,例如 SIGSEGV、SIGFPE、SIGILL。
如何注册信号处理函数
信号处理函数本质上是一个回调。当指定信号到来时,系统会转而执行你注册的处理函数。C 语言通过 signal() 完成这件事。
处理函数原型
void handler(int sig);
其中,sig 表示接收到的信号编号。常见做法是在这个函数里执行必要的清理、记录,或者决定程序是否退出。
signal() 的基本用法
注册时,需要把某个信号和对应处理函数关联起来。如果设置失败,signal() 会返回 SIG_ERR,因此示例里也会顺手做错误检查。
一个处理 SIGINT 的基础示例
下面这段代码演示了如何为 SIGINT 安装处理函数。程序启动后会一直运行;当用户按下 Ctrl+C 时,处理函数被触发,输出提示并退出程序。

// www.ja vascriptcn.com code example
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 定义信号处理函数
void handle_interrupt(int sig) {
printf("接收到中断信号 %dn", sig);
// 清理资源,保存状态等
exit(EXIT_SUCCESS); // 正常退出
}
int main() {
// 设置信号处理函数
if (signal(SIGINT, handle_interrupt) == SIG_ERR) {
perror("无法设置信号处理函数");
exit(EXIT_FAILURE);
}
printf("程序正在运行...n");
while (1) {
sleep(1);
}
return 0;
}
这段示例体现了信号处理的最小闭环:
- 包含头文件
<signal.h>; - 定义一个符合原型的处理函数;
- 调用
signal(SIGINT, handle_interrupt)完成注册; - 程序运行期间等待信号到来;
- 用户按下
Ctrl+C后进入处理函数并退出。
为什么信号处理函数不能写得太随意
入门示例通常会让人觉得“收到信号后写什么都行”,但实际开发里,信号处理函数的约束很多,处理不当反而更容易引入问题。

- 非原子性:处理函数执行过程中本身可能被打断,共享数据需要谨慎访问。
- 不可重入性:有些标准库函数并不适合在信号处理函数里调用,可能带来未定义或不安全行为。
- 特殊上下文:信号处理发生在特殊执行上下文中,和普通函数调用场景不同,容易出现预期外结果。
因此,信号处理函数通常更适合做简短、明确的操作,比如设置状态、做最小化清理,或者发出退出信号,而不是在里面堆积复杂业务逻辑。
使用 时先建立这几个判断
<signal.h> 解决的是“程序如何响应异步事件”这个问题。对于基础学习阶段,重点不在于记住全部信号,而在于先搞清楚三件事:信号是谁发来的、你打算如何响应、这个响应动作是否适合放进信号处理函数里。
从本文这个例子出发,你至少可以建立一个可靠的入门认识:signal() 可以让程序接住像 SIGINT 这样的中断通知,但信号处理并不是普通回调机制,写法必须克制。真正进入工程场景后,信号往往只负责通知,复杂逻辑则应交给主流程或更稳妥的控制路径处理。













