3、单步追踪:step into、step over与step out的实战应用

调试器里最常用的三个命令,就是 step into、step over 和 step out。很多人用了好几年,其实没搞明白它们的真正区别。说白了,这三个操作决定了你「怎么走」和「走多深」。

我个人习惯,刚进一个函数时先用 step over 扫一遍。如果发现某个调用返回值不对劲,再回头用 step into 钻进去看。嗯,这个节奏很重要。

3.1 三个命令的本质区别

先看一张图,帮你快速建立直觉:

main() funcA() funcB() funcC() Step Over 不进入子函数 Step Into 进入子函数内部 Step Out 跳出当前函数 操作对比 Step Over (F10) — 不进入 Step Into (F11) — 进入 Step Out (Shift+F11) — 跳出 适用场景 Step Over: 快速扫描主逻辑 Step Into: 深入可疑函数 Step Out: 确认后快速返回

一句话总结:

  • Step Over (F10) — 执行当前行,遇到函数调用直接跳过,不进去。
  • Step Into (F11) — 执行当前行,遇到函数调用就钻进去。
  • Step Out (Shift+F11) — 直接执行完当前函数,返回到调用处。

3.2 Step Over — 快速扫描主逻辑

Step Over 是我用得最多的命令。为什么?因为它快。你想想看,调试一个几百行的函数,如果每遇到一个库函数都钻进去,那得看到猴年马月?

举个例子:

void process_data(uint8_t *buf, uint32_t len) {
    uint32_t crc = calculate_crc(buf, len);   // 第1行
    if (crc != EXPECTED_CRC) {                 // 第2行
        log_error("CRC mismatch");             // 第3行
        return;                                // 第4行
    }
    save_to_flash(buf, len);                   // 第5行
}

如果我用 Step Over 调试这段代码,按一次 F10 执行一行。到第1行时,calculate_crc 直接跑完,结果赋给 crc。我不需要知道它内部怎么算的,我只关心返回值对不对。

我的习惯:先用 Step Over 把整个函数走一遍。如果发现某个中间变量的值不对劲,再回头用 Step Into 去查那个具体的函数。这样效率最高。

3.3 Step Into — 深入可疑函数

Step Into 就像拿着放大镜。你怀疑某个函数有问题,就钻进去看个究竟。

我在项目中遇到过这样一个 bug:一个传感器驱动偶尔返回 -1,但大部分时候正常。用 Step Over 只能看到返回值是 -1,根本不知道里面发生了什么。于是我用了 Step Into:

int read_sensor(int16_t *value) {
    uint8_t buf[4];
    int ret = i2c_read(SENSOR_ADDR, REG_DATA, buf, 4);  // 钻进去!
    if (ret != 0) {
        return -1;
    }
    *value = (buf[0] << 8) | buf[1];
    return 0;
}

按 F11 进入 i2c_read 后,我发现问题出在超时处理上——硬件 I2C 在总线忙时会重试 3 次,但重试后没有重新启动传输。嗯,这就是 Step Into 的价值。

注意:Step Into 会进入所有函数调用,包括库函数。如果你不小心进了 printf 或 memset 的内部,别慌,用 Step Out 跳出来就行。

3.4 Step Out — 确认后快速返回

Step Out 是个「逃生门」。你钻进去看了几行,发现不是这里的问题,或者已经找到了想要的答案,那就没必要一行行走完整个函数。

举个例子:你 Step Into 了一个函数,看了前 5 行就确认了问题原因。剩下的 50 行你不想看了。按 Shift+F11,调试器会直接执行完这个函数,停在调用处的下一行。

void check_checksum(uint8_t *data, uint32_t len) {
    uint32_t sum = 0;
    for (uint32_t i = 0; i < len; i++) {
        sum += data[i];           // 看了这里,发现 data[0] 是 0xFF,不对
    }
    // 剩下的不想看了,按 Shift+F11 跳出去
    if (sum != data[len]) {
        // ...
    }
}

避坑指南:我曾经在调试一个递归函数时,不小心 Step Into 了 10 层。当时脑子一热,一直按 F11 想退出来,结果越陷越深。后来才想起来用 Step Out——一层一层跳,比硬退快得多。

3.5 实战组合拳

这三个命令不是孤立的。真正高效的调试,是组合使用。我总结了一个「三步法」:

步骤 操作 目的
1 Step Over 扫一遍 快速定位可疑的函数调用
2 Step Into 钻进去 深入分析问题根源
3 Step Out 跳出来 确认后快速返回主流程

你想想看,如果每次调试都只用 Step Into,那效率得多低?反过来,如果全程 Step Over,遇到 bug 又看不透。所以我的建议是:先粗后细,该进则进,该出则出

3.6 常见误区

  • 误区一:以为 Step Over 会跳过函数执行。不会的,函数照样执行,只是你看不到内部细节。
  • 误区二:在循环里一直按 Step Into。如果你进了 for 循环体,每次迭代都会停一次。想跳出循环?用 Step Out 或者直接设断点。
  • 误区三:Step Out 后以为函数没执行完。实际上函数执行完了,只是你没看到中间过程而已。

一句话记住:

Step Over 看结果,Step Into 看过程,Step Out 快脱身。三个配合好,调试没烦恼。


公众号:蓝海资料掘金营,微信deep3321