3、断点设置艺术:普通断点、条件断点、函数断点、临时断点

调试这事儿,说白了就是跟代码玩捉迷藏。而断点,就是你喊「停」的那只手。

我刚开始用GDB那会儿,只会一种断点——b main,然后一路next下去。后来被一个bug折磨了三天,才发现原来断点还有这么多门道。今天咱们就把这四种断点聊透。

3.1 普通断点:最基础的「暂停键」

普通断点,就是你在某一行代码上打个标记。程序跑到这一行,立刻停住。

用法很简单:

(gdb) break main.cpp:42
(gdb) b 42           # 简写,当前文件第42行
(gdb) b main.cpp:42  # 指定文件

我个人习惯用b这个简写,少敲几个字母,调试效率能高不少。你想想看,调试的时候谁愿意打那么多字?

小技巧:如果你不确定行号,可以用list命令先看看代码。我一般会先l一下,找到目标行,再b上去。

普通断点有个特点——每次运行到这儿都会停。这既是优点也是缺点。优点是你不会漏掉,缺点是你可能被烦死。比如循环体里打个断点,循环100次你就得按100次c。

3.2 条件断点:只在「对的时候」停下

条件断点,说白了就是给断点加个「门禁」。只有条件满足时,程序才会停。

我在项目中遇到过这样一个场景:一个链表遍历函数,处理了10万个节点,但只有第99999个节点会触发崩溃。用普通断点?手都按酸了。用条件断点?一行命令搞定。

(gdb) break main.cpp:42 if count == 99999
(gdb) b 42 if count > 1000 && flag == true

条件可以是任何C++表达式。变量、函数调用、甚至复杂的逻辑运算都行。

核心要点:条件断点不会降低程序运行速度太多。GDB只在每次执行到该行时检查条件,而不是每行都检查。所以放心用。

嗯,这里要注意一点:条件表达式里别用有副作用的函数。比如if ++count == 10这种,调试完你都不知道count变成多少了。我曾经犯过这个错,排查了半天才发现是断点条件改了变量值。

3.3 函数断点:不用找行号

函数断点,就是直接在函数入口处停下。你不需要知道函数在第几行,只需要知道函数名。

(gdb) break MyClass::processData
(gdb) b processData    # 简写,如果有重载会列出所有
(gdb) b *processData   # 在函数第一条指令处停(汇编级别)

函数断点有个特别实用的场景——调试第三方库。你想想看,你调用了Qt的QString::toUpper(),想知道它内部怎么处理的。你不需要去翻Qt源码找行号,直接b QString::toUpper就行。

函数断点也支持条件:

(gdb) b MyClass::processData if this->m_id == 42

这个在调试多线程或者对象池的时候特别好用。我只想停在这个对象的这个函数里,其他对象别来烦我。

注意:函数断点对重载函数会列出所有版本。你需要选择具体哪一个。比如b print可能会列出print(int)、print(string)等,你得选一个。

3.4 临时断点:用完即弃

临时断点,就是「一次性断点」。它停一次之后,自动消失。

用法:

(gdb) tbreak main.cpp:42
(gdb) tb 42           # 简写
(gdb) tbreak MyClass::processData

临时断点什么时候用?我举个例子。你正在调试一个递归函数,你想在递归深度为5的时候停下来看看。你可以这样:

(gdb) b factorial if depth == 5
(gdb) run
# 停住了,查看变量
(gdb) tbreak factorial if depth == 6
(gdb) continue
# 下次停就是在depth==6的时候,而且这个断点只用一次

说白了,临时断点就是「用完就扔」的断点。它不会污染你的断点列表。我经常用它来做「下一步」的标记。

3.5 四种断点对比

断点类型 命令 适用场景 我的建议
普通断点 b line 固定位置,每次都要停 最常用,但别滥用
条件断点 b line if cond 循环、大量数据、特定状态 调试利器,必学
函数断点 b func 不知道行号、第三方库 省去找行号的麻烦
临时断点 tb line 一次性检查、递归、跳转 用完即弃,干净利落

3.6 断点管理:别让断点失控

调试到一半,你可能打了十几个断点。怎么管理?

(gdb) info breakpoints   # 列出所有断点
(gdb) disable 2          # 禁用2号断点(不删除)
(gdb) enable 2           # 重新启用
(gdb) delete 2           # 删除2号断点
(gdb) clear main.cpp:42  # 清除该行的所有断点

我个人习惯用disable而不是delete。为什么?因为有时候你只是暂时不想用这个断点,后面可能还要用。删了还得重新打,多麻烦。

避坑指南:我曾经在调试一个大型Qt项目时,打了20多个断点,结果程序跑起来卡得要死。后来发现是条件断点里写了个复杂的正则表达式匹配,每次都要执行。从那以后,我养成了一个习惯——条件断点的条件尽量简单,能用整数比较就别用字符串匹配。

3.7 实战:断点组合拳

最后,分享一个我常用的调试套路。假设我在调试一个Qt的信号槽:

// 我想知道当用户点击按钮时,数据是怎么流转的
(gdb) b MainWindow::onButtonClicked    # 函数断点,入口
(gdb) tb MainWindow::onButtonClicked if this->m_counter > 10  # 临时条件断点
(gdb) run
# 第一次停在onButtonClicked,查看参数
(gdb) continue
# 第二次停在临时断点,此时m_counter > 10
# 临时断点自动删除,不会影响后续调试

你看,普通断点、函数断点、条件断点、临时断点,四种配合起来用,调试效率直接翻倍。

断点设置这事儿,说白了就是「在哪里停、什么时候停、停几次」的问题。掌握了这四种断点,你就能精准地控制程序的执行节奏。下次遇到bug,别急着加qDebug,先想想——我该用哪种断点?

断点设置艺术 · 知识体系 断点类型 普通断点 b main.cpp:42 条件断点 b 42 if count==99 函数断点 b MyClass::func 临时断点 tbreak main.cpp:42 特性 每次运行到该行都停 最基础、最常用 特性 无需知道行号 支持重载函数选择 断点管理 info / disable / enable / delete / clear 💡 四种断点配合使用,调试效率翻倍

可可精品资料库