调试是 C 语言开发中的核心技能,无论是查找内存泄漏还是分析崩溃原因,都需要掌握恰当的调试方法。本文介绍 GDB 调试、printf 调试技巧、Valgrind 内存检测以及性能分析方法。
GDB 调试入门
GDB 是 Linux 下最强大的命令行调试器,支持断点设置、变量查看、程序执行控制等功能。
基本启动
# 编译时添加 -g 选项生成调试信息
gcc -g -o program program.c
# 启动 GDB
gdb ./program
# 常用命令
(gdb) run # 运行程序
(gdb) break main # 在 main 函数设置断点
(gdb) break program.c:20 # 在指定文件和行号设置断点
(gdb) continue # 继续执行到下一个断点
(gdb) next # 单步执行(不进入函数)
(gdb) step # 单步执行(进入函数)
(gdb) print var # 打印变量值
(gdb) backtrace # 查看调用栈
(gdb) quit # 退出 GDB
条件断点与监视点
# 条件断点:当 i == 10 时暂停
(gdb) break program.c:15 if i == 10
# 监视点:变量值变化时暂停
(gdb) watch counter
# 查看断点列表
(gdb) info breakpoints
# 删除断点
(gdb) delete 1
printf 调试技巧
虽然 GDB 功能强大,但 printf 调试在某些场景下同样有效,特别是在复现问题时。
日志级别宏
#define DEBUG
#ifdef DEBUG
#define LOG(fmt, ...) fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", \
__FILE__, __LINE__, ##__VA_ARGS__)
#else
#define LOG(fmt, ...)
#endif
/* 使用示例 */
LOG("count = %d", count);
LOG("array[%d] = %s", i, arr[i]);
追踪函数调用
void enter_func(const char *name) {
printf("==> %s\n", name);
}
void exit_func(const char *name) {
printf("<== %s\n", name);
}
/* 在函数入口和出口调用 */
int calculate(int n) {
enter_func("calculate");
/* 函数逻辑 */
exit_func("calculate");
return result;
}
Valgrind 内存检测
Valgrind 是检测内存错误的利器,可以发现内存泄漏、未初始化内存访问等问题。
安装与基本使用
# 安装 Valgrind(Debian/Ubuntu)
sudo apt install valgrind
# 运行 memcheck 工具检测内存问题
valgrind --leak-check=full ./program
# 常用选项
valgrind --leak-check=full --show-leak-kinds=all ./program
valgrind --track-origins=yes ./program # 追踪未初始化内存来源
常见问题与修复
1. 内存泄漏
/* 有泄漏的代码 */
void leak_example(void) {
char *buf = malloc(256);
strcpy(buf, "data");
/* 缺少 free(buf) */
}
/* 修复后 */
void no_leak_example(void) {
char *buf = malloc(256);
if (!buf) return;
strcpy(buf, "data");
free(buf); /* 释放内存 */
}
2. 未初始化内存
/* 有问题的代码 */
int uninit_example(void) {
int arr[10];
int sum = arr[0]; /* arr 未初始化 */
}
/* 修复后 */
int init_example(void) {
int arr[10] = {0}; /* 初始化为 0 */
int sum = arr[0];
}
3. 写入越界
/* 有问题的代码 */
void overflow_example(void) {
int arr[5];
for (int i = 0; i <= 5; i++) { /* 边界错误 */
arr[i] = i;
}
}
/* 修复后 */
void no_overflow_example(void) {
int arr[5];
for (int i = 0; i < 5; i++) { /* 正确边界 */
arr[i] = i;
}
}
性能分析
使用 gprof 和 perf 可以分析程序性能瓶颈。
gprof 使用
# 编译时添加 -pg 选项
gcc -pg -o program program.c
# 运行程序(会在当前目录生成 gmon.out)
./program
# 分析性能数据
gprof ./program gmon.out > profile.txt
perf 使用
# 安装 perf
sudo apt install linux-tools-common
# 记录程序执行
perf record -g ./program
# 查看报告
perf report
调试案例
段错误(Segmentation Fault)
/* 常见原因:空指针解引用 */
char *ptr = NULL;
strcpy(ptr, "test"); /* 段错误 */
/* 调试方法:使用 GDB 查看崩溃位置 */
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401154 in main () at program.c:10
(gdb) bt # 查看调用栈
内存访问错误
/* 常见原因:使用已释放的内存 */
char *buf = malloc(256);
free(buf);
strcpy(buf, "test"); /* 使用已释放内存 */
/* 调试方法:使用 Valgrind */
valgrind --leak-check=full ./program
# 输出:Invalid free() / delete / delete[]
总结
- GDB:强大的命令行调试器,适合复杂问题调试
- printf:简单直接,适合快速排查
- Valgrind:内存问题检测神器,必备工具
- 性能分析:gprof 和 perf 帮助定位性能瓶颈
掌握这些调试技巧,可以显著提升 C 语言开发效率,快速定位和解决问题。