第4章 断点设置:让程序在你想停的地方停下来
调试这件事,说白了就是让程序按你的意愿运行。
你想让它跑,它就跑。你想让它停,它就停。而断点,就是那个让程序停下来的开关。
我个人习惯把断点比作「路障」。程序执行到路障前,会自动刹车。这时候你就可以检查变量、查看堆栈、分析状态。嗯,今天我们就来聊聊怎么设置这些路障。
4.1 break命令:最基础的断点
GDB里设置断点的命令是 break,简写为 b。用法很简单:
(gdb) break main.c:42
(gdb) b main.c:42 // 简写形式
这表示在 main.c 文件的第42行设置一个断点。程序执行到这一行时,就会停下来。
我记得刚学GDB那会儿,总觉得记命令很麻烦。后来发现,其实常用的就那几个。break、continue、next、step、print,五个命令能解决80%的问题。
info source 查看。我经常用这个命令确认自己没跑偏。
4.2 设置行号断点
行号断点是最直观的。你打开源码,看到某一行有问题,直接在那行设个断点就行。
// example.c
#include <stdio.h>
int main() {
int a = 10;
int b = 20;
int c = a + b; // 我想看看这行执行后c的值
printf("c = %d\n", c);
return 0;
}
在GDB里:
(gdb) b example.c:6
Breakpoint 1 at 0x4004f6: file example.c, line 6.
(gdb) run
Starting program: /home/user/example
Breakpoint 1, main () at example.c:6
6 int c = a + b;
程序停在了第6行。这时候你可以用 print a、print b 看看它们的值。
4.3 设置函数断点
有时候你不想关心具体在哪一行,只想在某个函数被调用时停下来。这时候用函数断点就方便了。
(gdb) b main
Breakpoint 1 at 0x4004e6: file example.c, line 4.
(gdb) b printf
Breakpoint 2 at 0x4004d0: file printf.c, line 45.
第一个断点设在 main 函数入口。第二个断点设在 printf 函数入口。
函数断点有个好处:你不需要知道函数定义在哪一行。只要知道函数名就行。我在调试大型项目时特别喜欢用这个,因为项目文件太多,找行号太费劲了。
你想想看,一个项目几百个源文件,你记得住每个函数在第几行吗?反正我记不住。用函数名断点,省心多了。
4.4 设置条件断点
条件断点,说白了就是带条件的断点。程序只有在满足条件时才会停下来。
举个例子:你有一个循环,循环1000次。你只想知道当 i == 500 时发生了什么。如果设普通断点,你得按500次 continue,手都按酸了。
条件断点就是来解决这个问题的:
// loop.c
#include <stdio.h>
int main() {
for (int i = 0; i < 1000; i++) {
// 一些处理逻辑
printf("i = %d\n", i);
}
return 0;
}
在GDB里:
(gdb) b loop.c:5 if i == 500
Breakpoint 1 at 0x4004f6: file loop.c, line 5.
(gdb) run
Starting program: /home/user/loop
...
Breakpoint 1, main () at loop.c:5
5 printf("i = %d\n", i);
(gdb) print i
$1 = 500
看到了吗?程序直接停在了 i == 500 的那一次。不用你手动按500次。
break [位置] if [条件]。位置可以是行号,也可以是函数名。条件可以是任何合法的C/C++表达式。
条件断点还有更高级的用法。比如你想监控一个指针是否为空:
(gdb) b my_function if ptr == NULL
或者你想监控一个变量是否超过某个阈值:
(gdb) b process_data if data_size > 1024
我曾经在调试一个网络协议栈时,遇到一个诡异的问题:数据包偶尔会丢失。用条件断点监控 packet->length == 0,一下子就找到了问题根源——某个分支忘记给长度赋值了。
4.5 断点的管理
断点设多了,有时候会忘记自己设了哪些。这时候可以用 info breakpoints 查看:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004004f6 in main at example.c:6
breakpoint already hit 1 time
2 breakpoint keep y 0x00000000004004d0 in printf at printf.c:45
3 breakpoint keep y 0x00000000004004f6 in main at loop.c:5
stop only if i == 500
breakpoint already hit 1 time
表格里每一列的含义:
| 列名 | 含义 |
|---|---|
| Num | 断点编号,从1开始 |
| Type | 断点类型,一般是breakpoint |
| Disp | 断点命中后的行为,keep表示保留,del表示删除 |
| Enb | 是否启用,y表示启用,n表示禁用 |
| Address | 断点的内存地址 |
| What | 断点的位置和条件 |
删除断点用 delete 命令:
(gdb) delete 1 // 删除编号为1的断点
(gdb) delete // 删除所有断点(会提示确认)
禁用断点用 disable,启用用 enable:
(gdb) disable 2 // 禁用编号为2的断点
(gdb) enable 2 // 重新启用
disable 比 delete 更合适。我经常在调试时临时禁用一些断点,等需要时再启用,省得重新设置。
4.6 本章知识体系
下面这张图总结了断点设置的核心内容:
4.7 避坑指南
最后分享几个我踩过的坑:
- 优化编译导致断点不准:用
-O2编译时,编译器可能会重排代码。断点设在第10行,实际停在第8行。我建议调试时用-O0 -g编译。 - 条件断点中的副作用:条件表达式里不要写有副作用的代码,比如
if i++ == 500。这会导致程序逻辑改变,我吃过这个亏。 - 断点太多影响性能:设太多断点,程序会变慢。尤其是条件断点,每次执行都要检查条件。我一般控制在10个以内。
- 共享库的断点:给共享库设断点,需要等库加载后才能设。可以用
break加函数名,GDB会自动处理。
好了,断点设置就讲到这里。记住一句话:断点是你和程序对话的窗口。设好断点,你就掌握了调试的主动权。
- break命令是设置断点的核心命令
- 行号断点精确到行,函数断点精确到函数入口
- 条件断点用
if关键字,只在条件满足时触发 - 用
info breakpoints管理断点,用delete和disable控制断点状态
可可精品资料库