跳到内容

第 0 卷 / 第 4 章 / 第 3 课

4.3 条件断点、调用栈与求值 ​

前置知识:断点、变量与单步执行

预计时间:15 分钟

练习环境:Python 调试器;文末 GDB 实验需要 C 编译器与 GDB

完成标志:能过滤暂停、切换栈帧,并控制求值副作用

普通断点已经找到零重量这条路径。数据量变大后,你还需要减少无关暂停,并从当前函数沿调用栈追到输入来源。

条件断点不是免费观察 ​

要只检查编号 99999 的记录,可把断点条件设为:

python
item["id"] == 99999

调试器每次经过该位置都要求值。高频循环可能显著变慢,条件也可能抛异常或改变状态。条件应尽量是便宜、只读的表达式,不调用写文件、发请求或修改对象的函数。命中次数和日志断点同样有开销。

调用栈与栈帧 ​

python
def get_discount_ratio(item_id):
    return 0 if item_id == 42 else 0.1

def get_alloy_discount(item_id, weight):
    ratio = get_discount_ratio(item_id)
    return weight * ratio

def calculate_cost(item):
    return item["weight"] - get_alloy_discount(item["id"], item["weight"])

print(calculate_cost({"id": 42, "weight": 100}))

把这段程序保存为 discount_trace.py,普通运行应输出 100;在 get_discount_ratio 的 return 行设断点,再调试启动。

在最内层暂停时,调用栈大致包含当前的 get_discount_ratio,以及它的调用者 get_alloy_discount、calculate_cost 和模块入口。不同界面可能把当前帧放在顶部或底部。选择调用者帧只会切换局部变量视图;程序仍停在同一现场,这不是执行历史回放。异步、多线程和优化代码的栈可能不完整或形态不同。

ratio == 0 只是观测值。必须结合需求判断编号 42 是正确特例、数据错误还是未实现逻辑。

Watch 与调试控制台 ​

Watch 适合纯表达式,例如 item["weight"] >= 0。函数调用可能再次命中断点、执行 I/O 或修改状态;递归调用也不保证报错,反而可能启动更多计算。调试控制台改变量可快速验证假设,但结论必须在未篡改的新进程中复现。

GDB 速览 ​

先确认两个工具存在,再在临时目录建立一个最小程序:

bash
command -v cc
command -v gdb
gdb_workdir=$(mktemp -d)
cd "$gdb_workdir"
cat > program.c <<'C'
#include <stdio.h>

int main(void) {
    int value = 42;
    printf("value=%d\n", value);
    return 0;
}
C
cc -Og -g program.c -o program
gdb ./program

进入 GDB 后:

text
break main
run
next
print value
backtrace
continue
quit

退出 GDB 后清理:

bash
cd
ls -ld -- "$gdb_workdir"
rm -r -- "$gdb_workdir"

GDB 和 LLDB 都常用于原生程序。-g 写入调试信息,-Og 保留适合调试的优化级别,但源码步骤仍不等于逐条机器指令。服务器环境还可使用 core dump、远程调试、日志与剖析工具,并非只有 GDB。命令细节可查 GDB 的 Continuing and Stepping 与 Backtraces。

→ 下一步:综合练习

Built with VitePress | Software Systems Atlas