资讯动态

C 语言常见段错误(Segmentation Fault)排查实战:从复现到定位

发布时间:2026/10/20 5:28:00 来源:尧图企业网站定制
一、引言在 Linux C 语言开发过程中段错误Segmentation Fault简称 Segfault是一类常见的运行时故障。当程序访问了不允许访问的内存区域或者进行了非法内存操作时操作系统可能向进程发送SIGSEGV信号导致程序异常终止。段错误通常不会直接告诉开发者具体是哪一行代码出现了问题。尤其是在嵌入式 Linux、ARM64 平台以及底层系统测试程序中内存错误可能发生在复杂的调用链中实际故障位置与最初引入问题的位置也可能不同。例如以下操作都可能引发段错误解引用空指针。访问已经释放的内存。数组越界访问。使用未初始化的指针。错误计算内存缓冲区长度。向无效地址写入数据。本文通过几个可复现的 C 语言示例介绍段错误的常见成因以及如何使用编译器诊断选项、GDB 和 AddressSanitizer 定位问题。二、什么情况下会发生段错误2.1 空指针解引用先看一个最简单的示例#include stdio.h int main(void) { int *ptr NULL; *ptr 100; printf(%d\n, *ptr); return 0; }这里ptr被明确设置为NULL随后程序尝试通过*ptr写入数据。空指针不指向有效的对象因此该操作属于未定义行为。在常见 Linux 系统上这通常会触发段错误。正确的处理方式是在使用指针之前确认它是否指向有效对象#include stdio.h #include stdlib.h int main(void) { int *ptr malloc(sizeof(*ptr)); if (ptr NULL) { perror(malloc); return EXIT_FAILURE; } *ptr 100; printf(%d\n, *ptr); free(ptr); ptr NULL; return EXIT_SUCCESS; }需要注意检查指针是否为NULL只能排除空指针这一类问题并不能证明指针一定有效。悬空指针、越界指针和已经失效的对象仍然可能引发内存错误。三、数组越界最容易被忽略的内存错误3.1 错误示例#include stdio.h int main(void) { int arr[5] {1, 2, 3, 4, 5}; for (int i 0; i 5; i) { printf(%d\n, arr[i]); } return 0; }数组arr只有 5 个元素有效下标为0到4。当i 5时程序访问arr[5]已经超出了数组边界。这种越界访问属于未定义行为。它不一定立即产生段错误也可能输出看似正常的数据破坏其他变量或者在后续代码中才暴露问题。3.2 修复方式for (int i 0; i 5; i) { printf(%d\n, arr[i]); }在实际工程中不建议依赖程序运行结果来判断是否越界。对于动态分配的缓冲区应明确记录缓冲区容量并在每次访问前检查索引和长度。四、释放内存后继续访问Use-After-Free4.1 错误示例#include stdio.h #include stdlib.h int main(void) { int *ptr malloc(sizeof(*ptr)); if (ptr NULL) { return EXIT_FAILURE; } *ptr 123; free(ptr); printf(%d\n, *ptr); return 0; }调用free(ptr)后程序不再拥有该内存对象的使用权。继续通过ptr读取数据属于释放后使用Use-After-Free。这类错误的危险之处在于释放后的内存可能暂时仍然保留原来的数据因此程序有时看起来能够正常运行但内存分配器可能已经将这块区域用于其他用途。4.2 修复方式free(ptr); ptr NULL;将指针设置为NULL有助于避免通过同一个指针再次访问但它无法自动修复其他指向同一内存区域的别名指针。因此工程上还需要明确内存所有权和释放责任。五、malloc() 返回值没有检查5.1 错误示例char *buffer malloc(1024 * 1024); buffer[0] A;如果内存分配失败malloc()会返回NULL。此时访问buffer[0]就可能触发段错误。正确写法#include stdio.h #include stdlib.h int main(void) { size_t size 1024 * 1024; char *buffer malloc(size); if (buffer NULL) { perror(malloc); return EXIT_FAILURE; } buffer[0] A; free(buffer); return EXIT_SUCCESS; }对于大型文件测试、循环读写程序和嵌入式系统测试工具必须检查动态内存分配是否成功。内存不足时应明确返回错误而不是继续执行后续操作。六、使用 GDB 定位段错误当程序运行后出现Segmentation fault第一步不是盲目修改代码而是尽可能保留现场并获取调用栈。6.1 编译时保留调试信息假设源文件名为segfault.cgcc -g -O0 -Wall -Wextra \ segfault.c -o segfault其中-g生成调试信息。-O0关闭优化便于观察变量和调用过程。-Wall -Wextra启用更多常见编译警告。6.2 使用 GDB 运行程序gdb ./segfault进入 GDB 后执行run如果程序发生段错误可以继续查看btbt用于输出调用栈可以帮助确定程序经过了哪些函数。接下来可以查看当前代码位置list查看局部变量info locals查看指针print ptr查看调用栈中某一层的代码上下文frame 1具体可查看的变量取决于调试信息、优化级别以及当前栈帧。6.3 直接分析已有的 Core Dump如果系统允许生成 Core Dump可以在启动程序前执行ulimit -c unlimited ./segfault程序崩溃后若系统配置允许且 Core Dump 成功生成可以使用gdb ./segfault core随后执行bt在服务器和嵌入式设备中如果无法交互式调试Core Dump 尤其有价值。不过Core Dump 是否生成还受到系统配置、权限、存储空间以及core_pattern等设置影响。七、使用 AddressSanitizer 自动检测内存错误对于越界访问、释放后使用等问题AddressSanitizerASan通常可以比单纯查看段错误信息更快地定位原因。编译命令gcc -g -O1 \ -fsanitizeaddress \ -fno-omit-frame-pointer \ -Wall -Wextra \ segfault.c -o segfault_asan运行./segfault_asan如果检测到受支持的内存访问错误ASan 通常会输出错误类型、访问地址以及相关调用栈。需要注意ASan 需要编译时插桩和运行时支持。它并不能检测所有类型的程序错误也不一定适用于所有受限的嵌入式目标环境。对于无法运行 ASan 的目标板可以先在架构匹配的开发环境或模拟环境中复现再使用目标平台的调试工具分析。八、段错误排查流程总结遇到段错误时可以按以下顺序排查保留现场记录完整错误信息、运行参数和输入数据。开启调试信息使用-g -O0编译可复现程序。获取调用栈使用 GDB 的bt找到异常调用路径。检查指针确认指针是否为空、是否已释放、是否超出对象范围。检查长度计算特别关注数组下标、malloc()大小、read()返回值和字符串终止符。使用检测工具尝试 AddressSanitizer 或其他内存检查工具。修复并复测不仅验证原来的崩溃消失还要检查边界条件和异常路径。对于 Linux C 语言测试程序还应重点检查文件操作返回值、动态缓冲区长度、循环终止条件以及错误路径中的资源释放逻辑。九、结语段错误只是操作系统暴露出来的故障现象并不等同于问题一定出现在崩溃所在的那一行代码。可靠的定位过程应结合代码审查、调试信息、调用栈和内存检测工具逐步缩小问题范围。尤其是在 Linux ARM64 和嵌入式系统中建立可复现的最小测试用例并对指针生命周期和缓冲区边界进行系统检查通常比依赖偶然的运行结果更有效。对于实际工程建议将编译警告、静态分析、自动化测试和内存检测纳入日常开发流程从源头减少内存访问错误。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑