2. GDB核心概念:程序状态、断点、单步执行、查看变量

调试,说白了就是让程序「停下来给你看」。

我刚开始用GDB时,总觉得它像个黑盒子。后来发现,只要搞懂四个核心概念——程序状态、断点、单步执行、查看变量——调试就变得很直观了。这就像学开车,先搞懂方向盘、油门、刹车和仪表盘,剩下的就是熟练度问题。

2.1 程序状态:你的程序现在「活」得怎么样?

程序在运行中,其实有几种状态。我个人习惯把GDB里的程序状态分成三类:

  • 运行态(Running):程序正在执行,你没法干预它。
  • 暂停态(Stopped):程序停在了某一行,等你发号施令。
  • 终止态(Exited):程序跑完了,或者崩溃了。

调试的核心,就是让程序从「运行态」进入「暂停态」。为什么?因为只有在暂停时,你才能看清它的内部状态——变量值、寄存器、堆栈信息。

重要概念:GDB中,程序暂停后,你看到的每一行代码、每一个变量,都是「那一刻的快照」。时间凝固了,你可以慢慢分析。

我在项目中遇到过最头疼的事:一个bug只在特定条件下出现,跑100次才复现一次。如果没有断点,你只能干瞪眼。有了断点,就能让程序在可疑位置「停下来等你看」。

2.2 断点:让程序在指定位置「刹车」

断点就是调试的「刹车踏板」。你告诉GDB:「跑到这一行,给我停下。」

2.2.1 设置断点的基本方法

(gdb) break main          # 在main函数入口设断点
(gdb) break 42            # 在当前文件的第42行设断点
(gdb) break file.c:100    # 在file.c的第100行设断点
(gdb) break func_name     # 在函数func_name入口设断点

嗯,这里要注意:断点可以加条件。比如你只想知道当某个变量等于特定值时才停下:

(gdb) break 42 if count == 5   # 当count等于5时,在第42行停下

我曾经调试一个内存泄漏问题,循环跑了10万次才出问题。要是没条件断点,我得手动按「continue」按到手抽筋。条件断点救了我一命。

2.2.2 断点的管理

命令 作用
info breakpoints 列出所有断点
disable 1 禁用编号为1的断点(不删除)
enable 1 启用编号为1的断点
delete 1 删除编号为1的断点
clear 42 清除第42行的断点

小技巧:我习惯用disable而不是delete。因为有时候调试到一半,发现刚才禁用的断点又需要了。删了就没了,禁用还能恢复。

2.3 单步执行:一步一步看程序怎么走

断点让程序停下来,单步执行则让你「一步一步走」。你想想看,如果程序是一本书,断点就是书签,单步执行就是逐字阅读。

2.3.1 两个核心命令

  • step(简写 s):步入。遇到函数调用,会进入函数内部。
  • next(简写 n):步过。遇到函数调用,直接执行完整个函数,不进入内部。

举个例子:

10:  int result = compute(a, b);   // 假设程序停在这一行
11:  printf("result = %d\n", result);

如果你用 step,GDB会跳进 compute 函数内部,一行一行执行。如果你用 next,GDB会直接执行完 compute,停在下一行(第11行)。

避坑指南:我曾经在调试一个库函数时用了step,结果一头扎进了glibc的源码里,全是汇编和宏,看得我头皮发麻。如果你只想看自己的代码,用next就好。想进函数内部看细节,再用step。

2.3.2 其他单步命令

命令 作用
finish 执行完当前函数,返回到调用处
until 行号 一直执行到指定行(跳过循环时很好用)
continue 恢复执行,直到下一个断点

我建议你多练练 until。比如你在一个循环里,想直接跳到循环结束后的那一行,用 until 行号 比一次次按 next 高效得多。

2.4 查看变量:看看程序「心里在想什么」

程序停下来了,接下来就是「问它问题」。GDB里查看变量很简单:

(gdb) print variable_name    # 打印变量的值
(gdb) p variable_name        # 简写
(gdb) p/x variable_name      # 以十六进制显示
(gdb) p/d variable_name      # 以十进制显示
(gdb) p/s variable_name      # 以字符串形式显示

你还可以查看复杂的数据结构:

(gdb) p *pointer             # 查看指针指向的内容
(gdb) p array[0..5]          # 查看数组的前6个元素
(gdb) p struct_var.field     # 查看结构体的某个字段

核心技巧:用 display 命令可以让变量「自动显示」。每次单步执行后,GDB会自动打印你指定的变量值,不用每次都手动输入 print。

(gdb) display count          # 每次停下时自动显示count的值
(gdb) display/x ptr          # 以十六进制自动显示ptr
(gdb) info display           # 查看所有自动显示的变量
(gdb) undisplay 1            # 取消编号为1的自动显示

我记得有一次调试一个链表操作,节点指针总是不对。我设了三个 display:当前节点地址、下一个节点地址、节点里的数据值。单步走了几步,立刻发现是插入操作里忘记更新前一个节点的 next 指针了。要是没有自动显示,我可能得反复敲十几遍 print。

2.5 知识体系总览

下面这张图,是我梳理的本章核心逻辑。你可以把它当作调试时的「思维地图」:

GDB调试核心概念 程序状态 断点 单步执行 查看变量 运行态 / 暂停态 / 终止态 break / 条件断点 / 管理 step / next / finish / until print / display / 格式化输出 核心思路:让程序暂停 → 观察状态 → 单步推进 → 反复验证 调试的本质,就是「控制执行流 + 检查程序状态」

2.6 实战小练习

光说不练假把式。我建议你打开一个简单的C程序,比如:

#include <stdio.h>

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main() {
    int result = factorial(5);
    printf("5! = %d\n", result);
    return 0;
}

用GDB加载它,然后:

  1. 在 factorial 函数入口设断点
  2. 用 step 一步步走进递归调用
  3. 用 print n 观察每次递归时参数的变化
  4. 用 backtrace 查看调用栈(下一章会细讲)

我的建议:别急着学高级技巧。先把这四个核心概念练熟——程序状态、断点、单步执行、查看变量。这就像学功夫先扎马步,马步稳了,后面的招式才使得出来。

调试不是玄学,是科学。你掌握了这些基础,就已经能解决80%的bug了。剩下的20%,我们后面慢慢聊。

可可精品资料库