2. GDB基础命令

说实话,很多Qt开发者对GDB的第一印象就是「黑乎乎的命令行,看着就头疼」。我当年刚接触时也一样,觉得有Qt Creator的图形化调试器就够了,干嘛要自讨苦吃?

直到有一次,我在调试一个复杂的信号槽连接问题时,图形界面死活跳不到我想要的位置,断点也总是被优化掉。折腾了两个小时,最后用GDB命令行十分钟就定位到了问题。嗯,从那以后我就老老实实把GDB基础命令练熟了。

核心要点:GDB的命令其实不多,常用的也就十几个。你不需要背下所有命令,但启动、断点、单步、查看变量、退出这五类操作,必须形成肌肉记忆。

2.1 启动程序:两种方式,各有千秋

启动调试有两种常见方式,我根据场景不同会换着用。

方式一:直接启动GDB并加载程序

gdb ./myQtApp

这种方式适合你明确知道要调试哪个程序。GDB启动后,会停在程序入口处,等你发号施令。

方式二:先启动GDB,再加载程序

gdb
(gdb) file ./myQtApp

我个人的习惯是先用方式一,因为少打一行命令。但如果你需要调试core dump文件,就得用方式二了:

gdb ./myQtApp core.12345

小技巧:启动时加上 -q 参数可以去掉GDB的版本信息,界面更清爽:gdb -q ./myQtApp

启动之后,用 run 命令让程序跑起来。如果程序需要命令行参数,直接跟在后面:

(gdb) run --style fusion --geometry 800x600

2.2 设置断点:让程序停在你想停的地方

断点是调试的灵魂。没有断点,你只能眼睁睁看着程序跑完,啥也查不到。

按函数名设置断点

(gdb) break QWidget::show
(gdb) b MainWindow::onButtonClicked   // b 是 break 的简写

这在调试Qt信号槽时特别有用。我曾经遇到一个按钮点击没反应的问题,直接在槽函数入口设断点,发现根本没进来——原来是信号连接写错了。

按文件和行号设置断点

(gdb) break mainwindow.cpp:42
(gdb) b main.cpp:15

这种方式更精确,适合你知道问题大概在哪个文件哪一行。

条件断点

(gdb) break mainwindow.cpp:42 if counter > 5

这个我太爱用了。比如循环执行100次,你只关心第50次的情况,加个条件断点,省得你一次次按 continue。

注意:Qt的某些函数可能被内联或优化掉,导致断点无效。这时候可以尝试在函数定义处加 __attribute__((noinline)),或者编译时加上 -O0 关闭优化。

查看当前所有断点:

(gdb) info breakpoints

删除断点:

(gdb) delete 1   // 删除编号为1的断点
(gdb) delete      // 删除所有断点

2.3 单步执行:一步步看清程序的脉络

断点停住后,就该单步执行了。这里有几个命令,我刚开始时经常搞混。

命令 简写 作用 我的使用场景
step s 进入函数内部 想看某个函数的具体实现时
next n 不进入函数,直接执行完 确认函数返回值正确,不想看内部细节
finish fin 执行完当前函数并返回 不小心 step 进去了,想快速出来
continue c 继续执行到下一个断点 跳过不关心的代码段

举个例子。假设我在 MainWindow::onButtonClicked 里设了断点,程序停住后:

(gdb) n    // 执行当前行,不进入函数
(gdb) s    // 进入函数内部
(gdb) fin  // 执行完当前函数,回到调用处

我的习惯:先 n 几次确认大方向没错,遇到可疑的函数调用再用 s 进去看。别一上来就 s,否则可能一头扎进Qt的底层实现里,半天出不来。

2.4 查看变量:找到问题的关键证据

断点停住了,单步也执行了,接下来就是看变量值。这是调试的核心目的——找到哪里不对劲。

查看单个变量

(gdb) print counter
(gdb) p str          // p 是 print 的简写
(gdb) p m_widget->width()

查看数组或容器

(gdb) p *array@10    // 查看数组的前10个元素
(gdb) p myVector     // Qt的QVector可以直接打印

查看Qt对象属性

(gdb) p *this
(gdb) p m_label->text().toStdString()  // 查看QLabel的文本

这里有个坑:Qt的 QString 直接 print 会显示一堆内部结构,我一般用 .toStdString() 转成C++字符串再看。

查看局部变量列表

(gdb) info locals

这个命令会列出当前函数的所有局部变量及其值,省得一个个 print。

避坑指南:我曾经调试一个Qt程序,发现某个 QString 变量显示为 <optimized out>。这是因为编译器优化掉了这个变量。解决办法:编译时加 -O0 或者用 volatile 关键字。

2.5 退出GDB:优雅地结束调试

调试完了,怎么退出?

(gdb) quit
(gdb) q    // 简写

如果程序还在运行,GDB会问你是否要终止。输入 y 确认即可。

有时候程序卡死了,quit 没反应,可以试试:

(gdb) kill    // 先杀掉调试进程
(gdb) quit    // 再退出

小技巧:如果调试过程中想临时回到shell执行命令,可以用 shell 命令:

(gdb) shell ls -la
(gdb) shell g++ --version

执行完后输入 exit 回到GDB。

2.6 本章知识体系

下面这张图总结了GDB基础命令的核心流程,我建议你把它存下来,调试时对照着用。

GDB基础命令核心流程 启动程序 设置断点 运行程序 (run) 单步执行 (step/next) 查看变量 (print/info) 常用命令速查 run [args] - 运行程序 break [file:]line - 设断点 step (s) - 进入函数 next (n) - 跳过函数 finish - 跳出函数 continue (c) - 继续执行 print (p) - 打印变量 info locals - 局部变量 info breakpoints - 断点列表 quit (q) - 退出GDB 提示:所有命令都支持Tab键自动补全

说白了,GDB基础命令就是一套「启动→设断点→运行→单步→查看→退出」的循环。你每次调试,基本都在这个循环里打转。熟练了之后,你会发现它比图形界面灵活得多——尤其是处理那些偶现的、复杂的、需要反复重现的bug时。

我个人建议,花一个下午的时间,拿一个简单的Qt程序,把上面这些命令挨个敲一遍。不用刻意去背,敲上三五次,手指就记住了。

一句话总结:GDB基础命令是调试的「最小可行技能集」。掌握了这五类命令,你已经能解决80%的调试问题了。

可可精品资料库