2、GDB基础命令:启动程序、设置断点、单步执行、查看变量、退出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基础命令就是一套「启动→设断点→运行→单步→查看→退出」的循环。你每次调试,基本都在这个循环里打转。熟练了之后,你会发现它比图形界面灵活得多——尤其是处理那些偶现的、复杂的、需要反复重现的bug时。
我个人建议,花一个下午的时间,拿一个简单的Qt程序,把上面这些命令挨个敲一遍。不用刻意去背,敲上三五次,手指就记住了。
一句话总结:GDB基础命令是调试的「最小可行技能集」。掌握了这五类命令,你已经能解决80%的调试问题了。
可可精品资料库