C语言调试技巧完全指南

工程化调试

调试是 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 语言开发效率,快速定位和解决问题。