news 2026/4/17 7:27:45

【数据结构与算法】第49篇:代码调试技巧与常见内存错误排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【数据结构与算法】第49篇:代码调试技巧与常见内存错误排查

一、常见内存错误类型

错误类型表现常见原因
段错误程序崩溃,Segmentation fault访问空指针、越界访问、栈溢出
内存泄漏程序内存持续增长,最终耗尽malloc后忘记free
重复释放程序崩溃或行为异常对同一指针多次free
野指针随机崩溃使用已释放的指针
缓冲区溢出数据被覆盖,逻辑错误数组越界写入

二、段错误(Segmentation Fault)排查

2.1 什么是段错误

程序试图访问不属于它的内存区域时,操作系统会发送SIGSEGV信号,导致程序崩溃。

常见触发场景:

  • 解引用空指针:int *p = NULL; *p = 10;

  • 数组越界:int arr[5]; arr[5] = 10;

  • 使用已释放的内存

  • 栈溢出:递归过深或局部变量过大

2.2 使用GDB调试(Linux)

GDB(GNU Debugger)是Linux下最常用的调试工具。

bash

# 编译时加 -g 选项保留调试信息 gcc -g -o program program.c # 启动GDB gdb ./program # 常用命令 (gdb) run # 运行程序 (gdb) bt # 查看调用栈(backtrace) (gdb) list # 查看源代码 (gdb) print var # 打印变量值 (gdb) break 行号 # 设置断点 (gdb) continue # 继续运行 (gdb) quit # 退出

实战示例:调试段错误

c

#include <stdio.h> #include <stdlib.h> void causeSegfault() { int *p = NULL; *p = 10; // 这里会段错误 } int main() { causeSegfault(); return 0; }

bash

$ gcc -g -o test test.c $ gdb ./test (gdb) run Program received signal SIGSEGV, Segmentation fault. 0x0000000000401136 in causeSegfault () at test.c:5 5 *p = 10; (gdb) bt #0 causeSegfault () at test.c:5 #1 main () at test.c:9

bt命令直接告诉你:第5行*p = 10出问题了,调用链是 main → causeSegfault。

2.3 使用printf调试(快速定位)

当没有调试器时,在可疑位置插入printf是最原始有效的方法:

c

printf("debug: before line %d\n", __LINE__); // 可疑代码 printf("debug: after line %d\n", __LINE__);

如果"after"没打印出来,说明问题在这两行之间。

2.4 使用断言(assert)

断言可以帮助你在开发阶段提前发现问题:

c

#include <assert.h> int *p = (int*)malloc(sizeof(int)); assert(p != NULL); // 如果p为NULL,程序停止并输出错误位置 *p = 10;

三、内存泄漏排查(Valgrind)

3.1 安装Valgrind

bash

# Ubuntu/Debian sudo apt install valgrind # CentOS/RHEL sudo yum install valgrind # macOS brew install valgrind

3.2 基本使用

bash

valgrind --leak-check=full ./program

Valgrind会运行你的程序,并在结束时报告内存泄漏情况。

3.3 实战示例

c

#include <stdio.h> #include <stdlib.h> void memoryLeak() { int *p = (int*)malloc(10 * sizeof(int)); // 忘记 free(p) } int main() { memoryLeak(); return 0; }

bash

$ gcc -g -o test test.c $ valgrind --leak-check=full ./test ==12345== HEAP SUMMARY: ==12345== in use at exit: 40 bytes in 1 blocks ==12345== total heap usage: 1 allocs, 0 frees, 40 bytes allocated ==12345== ==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==12345== at 0x4848899: malloc (in /usr/lib/valgrind/...) ==12345== by 0x10916B: memoryLeak (test.c:5) ==12345== by 0x10917B: main (test.c:10)

关键信息:

  • 40 bytes in 1 blocks are definitely lost:确认泄漏

  • 直接告诉你泄漏发生在test.c:5malloc调用

3.4 Valgrind常用选项

选项作用
--leak-check=full详细泄漏报告
--show-reachable=yes显示仍可访问的泄漏
--track-origins=yes追踪未初始化变量的来源
--log-file=val.log输出到文件

四、使用VS调试器(Windows)

4.1 设置断点

在代码行号左侧单击,出现红点即断点。

4.2 常用快捷键

快捷键作用
F5开始调试/继续
F10单步执行(不进入函数)
F11单步执行(进入函数)
Shift+F5停止调试
Ctrl+F10运行到光标处

4.3 查看内存

调试时打开“内存窗口”(调试 → 窗口 → 内存),输入地址如&arr即可查看数组内容。


五、常见内存错误的预防

5.1 指针使用规范

c

// 1. 初始化指针为NULL int *p = NULL; // 2. 使用前检查 if (p == NULL) { // 处理错误 } // 3. free后置NULL free(p); p = NULL; // 4. 不要返回局部变量的地址 int* badFunc() { int local = 10; return &local; // 错误!局部变量在函数返回后销毁 }

5.2 数组边界检查

c

// 不安全 void unsafeCopy(char *dest, char *src) { while (*src) { *dest++ = *src++; // 不知道dest有多大 } } // 安全:传入长度 void safeCopy(char *dest, size_t destSize, char *src) { size_t i = 0; while (src[i] != '\0' && i < destSize - 1) { dest[i] = src[i]; i++; } dest[i] = '\0'; }

5.3 内存分配后检查

c

int *p = (int*)malloc(n * sizeof(int)); if (p == NULL) { fprintf(stderr, "内存分配失败\n"); exit(1); }

六、调试技巧总结

问题排查方法工具
段错误查看调用栈GDBbt
内存泄漏运行泄漏检测Valgrind
数组越界添加边界检查AddressSanitizer
未初始化变量追踪来源Valgrind--track-origins=yes
死循环打印日志printf / 断点
逻辑错误单步调试GDB / VS

七、AddressSanitizer(更现代的选择)

GCC和Clang内置了AddressSanitizer,比Valgrind更快,适合大型程序。

bash

# 编译时加上 -fsanitize=address gcc -g -fsanitize=address -o program program.c # 直接运行,错误时会输出详细报告 ./program

八、小结

这一篇我们学习了调试和内存错误排查:

工具用途核心命令
GDB调试段错误、逻辑错误gdb ./prog,run,bt
Valgrind检测内存泄漏valgrind --leak-check=full
AddressSanitizer检测内存错误-fsanitize=address
printf快速定位打印关键位置

预防胜于治疗

  • 指针初始化并置NULL

  • free后立即置NULL

  • 数组操作检查边界

  • 分配内存后检查返回值

下一篇我们讲专栏总结与面试高频考点。


九、思考题

  1. 段错误一定是程序有bug吗?还有哪些情况可能导致段错误?

  2. Valgrind报告"definitely lost"和"possibly lost"有什么区别?

  3. 如何用GDB在程序崩溃时自动打印调用栈?

  4. 为什么free(p)后要把p设为NULL

欢迎在评论区讨论你的答案。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 7:26:37

模力方舟:中国AI生态的自主创新样本

在全球AI基础设施领域&#xff0c;一个来自中国的开源平台正在书写独特的发展故事。模力方舟——这个诞生于2024年初的AI模型托管平台&#xff0c;最初被寄予厚望成为"中国版Hugging Face"&#xff0c;却在短短两年间走出了一条自主创新之路。从单纯模型托管到全流程…

作者头像 李华
网站建设 2026/4/17 7:25:38

学Simulink——基于Simulink的开关电容变换器电压均衡控制

目录 手把手教你学Simulink——基于Simulink的开关电容变换器电压均衡控制​ 摘要​ 一、背景与挑战​ 1.1 为什么需要主动电压均衡?​ 1.2 开关电容变换器(SCC):能量的“摆渡车”​ 1.3 破局之道:闭环电压均衡控制​ 二、系统架构与核心控制推导​ 2.1 整体架构:…

作者头像 李华
网站建设 2026/4/17 7:25:37

基于Simulink的硬件在环(HIL)电机控制器测试平台

目录 手把手教你学Simulink ——基于Simulink的硬件在环(HIL)电机控制器测试平台 一、引言:为什么“纯仿真”不够?真实控制器必须经受HIL考验! 二、什么是电机HIL?核心架构解析 HIL基本原理 三大核心优势: 三、应用场景:伺服驱动器量产前的全面验证 四、建模与实…

作者头像 李华
网站建设 2026/4/17 7:25:05

C语言动态内存管理

1.为什么要动态内存分配 C语言中的动态内存开辟&#xff0c;可以让程序员自己可以申请和释放空间&#xff0c;可以随意地调整内存空间&#xff0c;比较灵活。 2.malloc 和 free malloc void* malloc (size_t size);功能&#xff1a; 向内存申请一块连续可用的空间&#xff0c;并…

作者头像 李华
网站建设 2026/4/17 7:23:43

实时手机检测-通用效果可视化:热力图+边界框+置信度三重结果展示

实时手机检测-通用效果可视化&#xff1a;热力图边界框置信度三重结果展示 1. 引言&#xff1a;为什么需要更直观的手机检测结果&#xff1f; 想象一下&#xff0c;你正在开发一个智能会议室管理系统&#xff0c;需要自动检测参会者是否在会议期间使用手机。传统的检测模型可…

作者头像 李华
网站建设 2026/4/17 7:23:43

如何轻松实现B站视频转文字:Bili2text的完整解决方案

如何轻松实现B站视频转文字&#xff1a;Bili2text的完整解决方案 【免费下载链接】bili2text Bilibili视频转文字&#xff0c;一步到位&#xff0c;输入链接即可使用 项目地址: https://gitcode.com/gh_mirrors/bi/bili2text 你是否曾为整理B站视频中的知识点而烦恼&…

作者头像 李华