SIGSEGV 是一个非常重要的信号,理解它对于编写健壮的 C 程序至关重要。

什么是 SIGSEGV?
SIGSEGV 的全称是 "Segmentation Violation",即 “段错误”,当程序试图访问一块它没有权限访问的内存区域时,操作系统就会向该程序发送 SIGSEGV 信号。
这通常意味着程序犯了一个严重的内存访问错误,默认情况下,收到 SIGSEGV 的进程会被操作系统终止,并打印类似 "Segmentation fault" 的错误信息。
为什么会发生 SIGSEGV?(常见原因)
SIGSEGV 的核心原因是越界访问,以下是几个最常见的原因:
解引用空指针
这是最经典、最常见的段错误原因,空指针是一个指向 0 地址的指针,这个地址通常是保留给操作系统使用的,用户程序无权访问。

#include <stdio.h>
int main() {
int *p = NULL; // p 是一个空指针
*p = 100; // 尝试向 0 地址写入数据,导致段错误
return 0;
}
执行结果:
Segmentation fault (core dumped)
解引用未初始化的指针
一个局部指针变量如果未被初始化,它的值是随机的(一个“野指针”),解引用这个指针就像是随机访问内存,很大概率会访问到非法区域。
#include <stdio.h>
int main() {
int *p; // p 未初始化,是一个野指针
*p = 100; // 尝试向一个随机的、非法的地址写入数据
return 0;
}
执行结果:
Segmentation fault (core dumped)
数组越界访问
访问数组元素时,索引超出了数组的合法范围(从 0 到 size-1),这会导致你访问了数组后面的内存,而这些内存你可能没有权限访问。

#include <stdio.h>
int main() {
int arr[5] = {0}; // 数组大小为 5,合法索引是 0, 1, 2, 3, 4
int i;
for (i = 0; i <= 5; i++) { // 循环条件 i <= 5 导致了越界
arr[i] = i; // 当 i=5 时,访问 arr[5],这是非法的
}
return 0;
}
执行结果:
Segmentation fault (core dumped)
栈溢出
每个线程或进程都有一个有限的栈空间,用于存储局部变量、函数调用信息等,如果函数调用层级太深,或者定义了过大的局部变量(比如巨大的数组),就会耗尽栈空间,导致栈溢出。
#include <stdio.h>
void recursive_function() {
int big_array[10000]; // 每次调用都分配一个很大的局部数组
recursive_function(); // 无限递归
}
int main() {
recursive_function();
return 0;
}
执行结果:
Segmentation fault (core dumped)
访问已释放的内存
当你调用 free() 或 delete 释放一块内存后,这块内存就不再属于你的程序了,如果你再通过原来的指针去访问它,就是非法的。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
printf("Value: %d\n", *p);
free(p); // 释放 p 指向的内存
// p 变成一个“悬垂指针”(dangling pointer)
*p = 20; // 尝试访问已释放的内存,可能导致段错误
printf("New value: %d\n", *p);
return 0;
}
注意: 这个程序可能不会立即崩溃,因为操作系统可能暂时没有回收那块内存,但这被称为“未定义行为”,非常危险,程序在任何时候都可能崩溃。
如何调试 SIGSEGV?
段错误的调试是 C/C++ 开发中的一个核心技能。
使用 gdb (GNU Debugger)
gdb 是最强大的调试工具,当你用 gdb 运行你的程序时,它可以在程序崩溃时停下来,并告诉你错误发生的确切位置。
-
编译时加上
-g选项,以包含调试信息。gcc -g your_program.c -o your_program
-
使用
gdb运行程序gdb ./your_program
-
在
gdb中运行程序(gdb) run程序会像正常一样运行,直到遇到段错误。
-
程序崩溃后,使用
backtrace(或其别名bt) 命令(gdb) run Starting program: /path/to/your_program Program received signal SIGSEGV, Segmentation fault. 0x000000000040054d in main () at your_program.c:7 7 *p = 100; (gdb) bt #0 0x000000000040054d in main () at your_program.c:7backtrace会打印出函数调用栈,从栈顶(#0)开始,可以看到崩溃发生在main函数的第7行,这直接定位了问题代码。
使用 valgrind
valgrind 是一个更高级的内存调试工具,它能够检测出很多 gdb 看不出来的内存问题,
- 使用未初始化的内存
- 内存泄漏
- 访问已释放的内存
- 数组越界
使用方法:
valgrind --leak-check=full ./your_program
valgrind 的输出非常详细,它会明确告诉你哪一行代码导致了问题。
如何处理 SIGSEGV 信号?(高级用法)
虽然通常我们不希望程序在遇到段错误后继续执行(因为程序状态可能已损坏),但在某些特殊场景下,比如编写一个守护进程或服务器,你可能希望优雅地处理这个错误。
这可以通过信号处理函数来实现。
工作原理:
- 使用
signal()或sigaction()函数注册一个信号处理函数。 - 当
SIGSEGV发生时,操作系统会中断当前程序的执行,转而调用你注册的处理函数。 - 在处理函数中,你可以进行日志记录、清理资源等操作。
- 处理完成后,你可以选择终止程序,或者尝试让它继续运行(极不推荐,非常危险)。
示例代码:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
// 信号处理函数
void handle_segfault(int sig) {
fprintf(stderr, "捕获到段错误!信号号: %d\n", sig);
// 在这里进行清理工作,比如关闭文件、释放网络连接等
exit(1); // 安全地退出程序
}
int main() {
// 注册信号处理函数
signal(SIGSEGV, handle_segfault);
int *p = NULL;
*p = 100; // 这行代码会触发段错误
printf("这行代码不会被执行,\n");
return 0;
}
执行结果:
捕获到段错误!信号号: 11
在这个例子中,程序没有直接崩溃并打印 "Segmentation fault",而是调用了我们的自定义处理函数,使得我们可以记录下错误信息后再退出。
| 特性 | 描述 |
|---|---|
| 名称 | SIGSEGV (Segmentation Violation),段错误 |
| 原因 | 访问了没有权限访问的内存区域。 |
| 常见诱因 | 解引用空指针、野指针、数组越界、栈溢出、访问已释放内存。 |
| 默认行为 | 操作系统终止进程,打印 "Segmentation fault"。 |
| 调试方法 | gdb: 定位崩溃的代码行。valgrind: 检测各种内存错误。 |
| 高级处理 | 使用 signal() 或 sigaction() 注册信号处理函数,用于优雅地处理和记录错误,然后退出。 |
核心思想: SIGSEGV 是你的程序在向你尖叫:“我正在访问一块不该碰的内存!” 最好的做法是通过严谨的代码和强大的调试工具(gdb, valgrind)来找到并修复这些错误,而不是简单地忽略或捕获它们。
