3、基本断点技巧:设置断点、删除断点、启用/禁用断点、条件断点
断点这东西,说白了就是调试器里的「暂停键」。你让程序跑着,它跑到某一行就乖乖停下来,等你检查变量、看堆栈。我刚开始用 GDB 的时候,觉得断点不就是个 break 嘛,有啥好学的?后来在 Qt 项目里踩了几次坑才发现——断点用得好不好,直接决定你调试效率差多少倍。
今天咱们就把断点的基本功捋一遍。嗯,都是最常用的操作,但每个点我都吃过亏,所以会多唠叨几句。
3.1 设置断点:break 命令的几种姿势
设置断点最基础的就是 break,简写 b。我个人习惯用简写,敲起来快。来看几种常见用法:
// 在源文件 main.cpp 的第 42 行设置断点
(gdb) break main.cpp:42
// 在函数入口设置断点
(gdb) break QPushButton::setText
// 在类成员函数设置断点(需要完整路径)
(gdb) break MyWidget::onButtonClicked
// 如果函数名有重载,GDB 会列出所有候选,让你选
(gdb) break MyClass::update
[列出多个重载版本]
这里有个坑——Qt 的信号和槽。你直接在信号函数上设断点,有时候根本不会命中。为什么?因为信号本质上是编译器生成的代码,不是直接调用的。我建议你在槽函数上设断点,或者用 break QMetaObject::activate 来拦截所有信号发射。
info functions 函数名 搜一下。GDB 会列出所有匹配的函数签名,包括参数类型。
3.2 删除断点:别让旧断点干扰你
调试久了,断点列表会变得很乱。我记得有一次调试一个 Qt 的绘图事件,设了七八个断点,结果每次重绘都停好几次,烦得要死。这时候就需要清理一下。
// 删除编号为 2 的断点
(gdb) delete 2
// 删除所有断点(慎用!)
(gdb) delete
// 查看当前所有断点
(gdb) info breakpoints
info breakpoints 的输出里,第一列就是断点编号。我一般先看一眼列表,再决定删哪个。千万别手滑 delete 全删了——我曾经在调试一个复杂的内存泄漏时干过这事,结果重新设断点花了十分钟,心态直接崩了。
3.3 启用/禁用断点:临时关掉比删掉更聪明
有时候你不想删断点,只是暂时不想让它触发。比如你调试一个循环,前几次没问题,想跳过前面几次,只停在第 100 次。这时候禁用比删除好。
// 禁用编号为 3 的断点
(gdb) disable 3
// 重新启用
(gdb) enable 3
// 启用所有断点
(gdb) enable
我个人习惯是:调试初期多设断点,调试后期逐步禁用。因为有时候你删了断点,过一会儿发现还得用,又得重新找行号。禁用的话,留着编号,随时可以恢复。
3.4 条件断点:精准打击,不浪费时间
条件断点是我在 Qt 项目里用得最多的技巧。举个例子,你有一个列表,里面 1000 个元素,你想知道第 500 个元素处理时发生了什么。没有条件断点的话,你得手动按 499 次 continue……想想就崩溃。
// 当 i == 500 时停下
(gdb) break main.cpp:100 if i == 500
// 更复杂的条件:字符串比较
(gdb) break MyModel::data if itemName == "CriticalItem"
// 条件表达式支持 &&、||、!= 等
(gdb) break updateUI if errorCount > 0 && isVisible == true
这里有个容易踩的坑——Qt 的 QString 比较。在 GDB 里直接用 == 比较 QString 是不行的,因为 QString 不是基本类型。你需要用 strcmp 或者 GDB 的 Python 扩展。我一般这样写:
// 用 QString 的 toStdString 方法(需要 GDB 7.0+)
(gdb) break MyClass::process if myString.toStdString() == "target"
如果 GDB 版本低,那就只能手动检查了——在断点处停下来,用 print myString 看内容,然后决定要不要继续。
3.5 实战:用断点排查 Qt 界面卡顿
讲个真实案例。之前有个项目,Qt 界面在加载大文件时会卡住几秒钟。我怀疑是某个槽函数执行时间太长。于是我在槽函数入口设了断点,然后加了个条件:
// 假设槽函数是 onLoadFile
(gdb) break MainWindow::onLoadFile if fileSize > 1024 * 1024
这样只有大文件才会触发断点。停住之后,我用 step 单步执行,发现卡在了一个 QTextEdit::setPlainText 调用上。原来是一次性插入了太多文本,导致 UI 线程阻塞。后来改成分批插入,问题就解决了。
你看,一个条件断点,直接定位到问题根源。如果没有它,你可能得加一堆日志,或者靠猜。
3.6 断点管理小贴士
最后分享几个我常用的习惯:
- 给断点加注释:用
comment 编号 你的备注,方便记住每个断点的用途。 - 保存断点列表:用
save breakpoints 文件名,下次调试可以直接source 文件名恢复。 - 临时断点:用
tbreak设置一次性断点,命中一次后自动删除,适合只想停一次的场景。 - 硬件断点:如果软件断点不够用(比如 Flash 调试),可以用
hbreak设置硬件断点,数量有限但更稳定。
一句话总结: 断点不是越多越好,而是越精准越好。条件断点 + 禁用/启用组合,能让你在复杂项目中快速定位问题。
好了,断点的基本操作就这些。下一节咱们聊聊更进阶的——观察点、捕获点,还有怎么在 Qt 的信号槽机制里玩出花来。嗯,到时候见。