7.3 调试基础与断言

本篇学习断点、单步、观察变量、`assert` 和调试输出的基本方法,并能建立基础排错流程。

调试基础与断言

概念说明

调试不是“程序写完以后才做的补救动作”,而是理解程序真实执行过程的重要手段。 当代码开始涉及数组、指针、函数调用和文件读写时,只靠肉眼读源码往往已经不够了。

最常见的调试手段可以分成三类:

  • 调试器:断点、单步执行、查看变量、观察调用栈。
  • 断言:在关键位置检查程序内部假设是否成立。
  • 调试输出:临时打印路径、参数和中间结果,帮助定位问题范围。

调试的目标不是“盲猜哪里错了”,而是缩小问题范围。 先确认程序走到了哪一步,再确认关键变量是什么值,最后再确认它为什么变成这样。

语法/规则

  1. 调试构建通常建议保留符号信息,例如使用 -g,必要时降低优化级别,例如 -O0
  2. 断点适合回答“程序到底有没有走到这里”。
  3. 单步进入、单步跳过和单步跳出适合观察函数调用过程和局部变量变化。
  4. 观察窗口或监视表达式适合持续跟踪某个变量在循环和函数中的变化。
  5. assert 用来检查程序内部不应该被破坏的假设,而不是用来替代用户输入校验。
  6. 定义 NDEBUG 后,assert 通常会被禁用,因此不能把业务必需的运行时检查全放进断言里。
  7. 调试输出要尽量有上下文,例如输出变量名、阶段名和关键索引,而不是只打印一个孤立数字。

示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <assert.h>
#include <stdio.h>

#define DEBUG 1

#if DEBUG
#define DBG_PRINT(fmt, ...) fprintf(stderr, "[DEBUG] " fmt "\n", __VA_ARGS__)
#else
#define DBG_PRINT(fmt, ...)
#endif

int sum_positive(const int arr[], int len) {
    assert(arr != NULL);
    assert(len >= 0);

    int sum = 0;
    for (int i = 0; i < len; i++) {
        DBG_PRINT("i=%d value=%d", i, arr[i]);
        if (arr[i] > 0) {
            sum += arr[i];
        }
    }
    return sum;
}

int main(void) {
    int nums[] = {3, -1, 5};
    printf("sum = %d\n", sum_positive(nums, 3));
    return 0;
}

输出结果:

1
2
3
4
[DEBUG] i=0 value=3
[DEBUG] i=1 value=-1
[DEBUG] i=2 value=5
sum = 8

这个例子里,assert 负责检查函数前提条件,DBG_PRINT 负责把循环过程可视化。 两者搭配起来,排查逻辑错误会比只看最终结果轻松很多。

常见错误

  1. 一发现结果不对就盲改代码,却没有先确认程序到底走到了哪里。
  2. assert 当成用户输入校验的唯一手段,结果发布构建中断言被关闭后失去保护。
  3. 调试输出只打印一个数字,没有变量名、索引或阶段信息,导致信息价值很低。
  4. 调试时只看当前行,不看调用栈和前后状态变化,结果总在表面现象里打转。
  5. 排完问题以后忘记清理临时调试代码,导致正式输出被调试信息污染。
本文禁止转载
使用 Hugo 构建
主题 StackJimmy 设计 由 Hobin 魔改
最近构建时间:2026-04-17 19:07:48 CST
载入天数...载入时分秒...
发表了 1 篇文章 · 发表了 152 篇笔记 · 总计 18 万 0 千字