GDB调试器:你的C++程序“侦探”
较难4GDB调试器:你的C++程序“侦探”
当你写了一个C++程序,但运行结果和预想不一样时,该怎么办?GDB就是一位聪明的“侦探”,能帮你一步一步地查看程序里发生了什么,找到“凶手”(bug)。它就像你随身携带的慢镜头回放器,让程序的每一步都清晰可见。
?️ 安装和准备:给侦探配好工具包
大多数Linux系统已经自带GDB(可以用gdb --version检查)。如果没有,在终端输入sudo apt-get install gdb(Ubuntu/Debian)即可安装。
要让GDB正常工作,编译时必须带上**-g**选项,这相当于给程序“录下指纹”——把源代码的行号、变量名等信息嵌入到可执行文件中。没有这个选项,GDB就像瞎子一样,只能看到机器码,没法帮你找bug。
g++ -g myprogram.cpp -o myprogram
新手容易犯的错误:调试时忘记加
-g,导致run后看不到源代码,只能用stepi等底层指令,非常痛苦。记住:调试版必须加-g,发布版可以去掉以减小体积。
? 启动侦探模式
在终端输入gdb myprogram,就进入了GDB的交互界面,看到(gdb)提示符。这就像侦探坐在桌前,等着你发号施令。按Ctrl+D或输入quit可以退出。
? 常用的侦探指令(必须记住)
下面每个指令都配有生活例子和代码演示,方便你记忆。
1️⃣ 设置暂停点(断点)—— break 或 b
断点就像你在跑步时放了一个栏杆,程序跑到那里就会停下来等你检查。你可以按行号或函数名设置。
(gdb) break 10 # 在第10行停下来
(gdb) break main # 在main函数的第一行停下来
(gdb) break computeSum # 在computeSum函数的第一行停下来
生活中的例子:你记录零花钱支出,想知道每次买零食后余额是多少。你就可以在“余额更新”的那一行设置断点,每次停下来查看balance的值。
2️⃣ 开始跑程序 —— run 或 r
程序从第一行启动,跑到第一个断点就暂停。如果程序需要命令行参数,可以这样:run arg1 arg2。
(gdb) run # 不带参数运行
(gdb) run 5 10 # 给程序传递两个参数
3️⃣ 查看源代码 —— list 或 l
list显示当前暂停位置附近的源代码,让你知道自己在哪。
(gdb) list # 显示当前位置前后10行
(gdb) list 1,20 # 显示第1到第20行
4️⃣ 一步一步走:step 和 next
这两个指令是调试的核心,区别在于是否进入函数内部。
step(或s):像跟踪小偷进入小巷——如果当前行调用了函数,会进入函数内部,一行一行执行。next(或n):像大步跨过一条沟——把整行(包括函数调用)当作一步执行完,不进入函数内部。
新手容易犯的错误:在不需要看函数内部时使用了step,结果一头扎进库函数(如cout、vector的底层代码),半天出不来。遇到这种情况,可以用finish跳出当前函数,或者until 行号跳到指定行。
(gdb) step # 进入当前行调用的函数
(gdb) next # 不进入函数,直接执行完当前行
(gdb) finish # 执行完当前函数并返回,停在调用处
5️⃣ 查看变量值 —— print 或 p
这是最频繁使用的命令,让你看到变量当前的“真面目”。
(gdb) print i # 显示i的值
(gdb) print sum # 显示sum的值
(gdb) print &i # 显示i的地址(像侦探查指纹)
(gdb) print arr[3] # 显示数组arr的第4个元素
(gdb) print *ptr # 显示指针ptr指向的值
生活中的例子:你写了一个考勤程序,统计小明本月迟到次数。调试时可以用print lateCount查看当前次数,检查是不是每次加1。
另外,display命令可以设置自动显示,每次暂停时自动输出某些变量的值,省去反复输入print的麻烦。
(gdb) display i # 每次暂停都自动显示i
6️⃣ 继续跑完 —— continue 或 c
程序从暂停处继续执行,直到下一个断点或程序结束。
(gdb) continue
7️⃣ 退出侦探 —— quit 或 q
(gdb) quit
? 额外有用的指令
-
watch 变量名:当变量值发生变化时自动暂停(像设了一个“变化报警器”)。
比如watch sum,每次sum被修改就会暂停,非常适合追踪变量何时被意外改变。 -
backtrace(或bt):显示当前函数调用堆栈,就像看一个电话簿,知道当前是谁调用了谁。
当程序崩溃时,backtrace能告诉你崩溃发生在哪一层。 -
info locals:显示当前作用域内所有局部变量的值,快速浏览。
? 完整示例:找出累加错误并体验调试过程
假设你想计算1加到10,但结果却不对劲(比如总是多出一些)。原始代码故意埋了一个错误:
// sum.cpp
#include <iostream>
using namespace std;
int main() {
int sum = 0; // 存累加结果
for (int i = 1; i <= 10; i++) {
sum += i + 1; // 故意写错:本该是 sum += i
}
cout << "和是: " << sum << endl;
return 0;
}
调试过程(在终端逐行输入):
g++ -g sum.cpp -o sum # 带调试信息编译
gdb sum # 启动侦探
(gdb) break main # 在main函数开头设断点
(gdb) run # 运行,停在main第一行
(gdb) next # 执行到for循环的sum+=i+1那一行
(gdb) print i # $1 = 1,i是1,没问题
(gdb) print sum # $2 = 0,sum还没加
(gdb) next # 执行一次循环体,sum变成0+1+1=2
(gdb) print sum # $3 = 2,嗯,本来第一次应该加1得到1,现在多了1
(gdb) list # 看看源代码,发现sum += i + 1
(gdb) quit # 退出,去修改代码
预期正常行为:第一次循环,i=1,sum应变为0+1=1。但这里变成了2,因此断定是i+1惹的祸。
?? 新手常见错误汇总
| 错误行为 | 后果 | 正确做法 |
|---|---|---|
忘记加-g编译 | 无法设置断点、查看源码 | 编译时使用-g |
| 修改代码后没重新编译 | 调试的还是旧版本 | 每次修改后g++ -g ...重新编译 |
使用step进入cout等库函数 | 陷入冗长的库代码 | 用next跨过,或设置断点跳过 |
| 断点设在空行或注释上 | 无效断点,程序不停 | 检查行号对应有效代码 |
run时忘记传参数(如果程序需要) | 程序因缺参数崩溃 | 用run 参数1 参数2传参 |
? 相关指引
- GDB官方手册:在终端输入
info gdb或访问 GDB Documentation - 图形前端工具:
gdbgui、ddd、Code::Blocks内置调试器,让你用鼠标点击设置断点 - 进阶技巧:学会使用条件断点(
break 10 if i == 5)、检查内存(x/10x &arr)等,能更快定位复杂bug - 其他调试器:如果你使用Visual Studio,它的“断点”、“逐语句”功能与GDB本质相同,只是界面不同
调试就像玩侦探游戏,多练习就能又快又准地找到bug!下次遇到程序结果不对,试着启动GDB,你也能成为代码世界的“福尔摩斯”。
例题精讲
在GDB调试器中,若要在第10行的循环变量i等于5时暂停程序,应使用以下哪个命令设置条件断点?
GDB中,`next`命令和`step`命令的主要区别是什么?
在GDB中,可以使用`display`命令在每次程序暂停时自动显示指定变量或表达式的值。
在GDB调试过程中,要查看当前函数调用栈(即程序执行到当前位置时所有未被返回的函数调用序列),应使用命令 ___。假设有如下C++代码:
int a = 0;
for (int i = 0; i < 10; i++) {
a += i;
}
在GDB中,若要对变量a设置读观察点,即当a被读取时暂停程序,应使用命令 ___。