3、Gcov实战:运行程序生成.gcda文件、使用Gcov生成报告、解读Gcov报告、常见问题排查

好,咱们进入实战环节。前面两章我们把Gcov的原理和编译选项讲清楚了,这一章我带你亲手跑一遍完整的流程。从编译、运行、生成报告,到排查那些让人头疼的坑,咱们一步到位。

3.1 编译与运行:让.gcda文件诞生

先说说我的习惯。我一般会在Makefile里单独维护一个coverage编译目标,跟生产环境的编译选项完全分开。这样不会污染正式构建,也方便反复跑覆盖率。

编译时,必须加上两个关键flag:

gcc -fprofile-arcs -ftest-coverage -o test_program test.c

解释一下:

  • -fprofile-arcs:告诉编译器插入计数代码,记录每条边(分支)的执行次数
  • -ftest-coverage:生成.gcno文件,里面存的是代码的静态结构信息

编译完你会看到两个文件:test.gcno 和可执行文件 test_program。.gcno文件是静态的,编译一次就不变了。真正的动态数据,得等程序跑起来才会产生。

运行程序:

./test_program

跑完之后,当前目录下会多出一个 test.gcda 文件。这就是覆盖率分析的“原油”——里面记录了每一行代码、每一个分支的执行次数。

小技巧: 如果你跑完没看到.gcda文件,八成是程序异常退出了(比如段错误)。Gcov要求程序正常退出才能刷新缓冲区,把数据写盘。我遇到过好几次,排查半天发现是程序crash了,.gcda根本没生成。

3.2 使用Gcov生成报告

有了.gcda文件,接下来就是见证奇迹的时刻。运行:

gcov test.c

Gcov会读取 test.gcno 和 test.gcda,然后生成一个 test.c.gcov 文件。这个文件就是人类可读的覆盖率报告。

如果你想生成更友好的格式,可以用 -b 选项看分支覆盖率,用 -f 看每个函数的覆盖率:

gcov -b -f test.c

我个人习惯用 gcov -b -f -o . test.c,其中 -o . 指定.gcda文件的搜索路径。如果你的.gcda文件在别的目录,这个参数就很有用了。

3.3 解读Gcov报告

打开生成的 test.c.gcov 文件,你会看到类似这样的内容:

        -:    0:Source:test.c
        -:    0:Graph:test.gcno
        -:    0:Data:test.gcda
        -:    1:#include <stdio.h>
        1:    2:int main() {
        1:    3:    int x = 10;
        1:    4:    if (x > 5) {
        1:    5:        printf("x is greater than 5\n");
        -:    6:    } else {
    #####:    7:        printf("x is not greater than 5\n");
        -:    8:    }
        1:    9:    return 0;
        -:   10:}

每一行前面的数字代表执行次数:

  • 数字(如 1):该行被执行了多少次
  • #####:该行从未被执行过(未覆盖)
  • -:非可执行行(注释、空行、大括号等)

你看第7行,前面是 #####,说明else分支从来没走到过。这就是典型的“分支未覆盖”。

核心指标:
  • 行覆盖率:被执行的代码行数 / 总可执行行数
  • 分支覆盖率:被覆盖的分支数 / 总分分支数
  • 函数覆盖率:被调用的函数数 / 总函数数

我一般先看函数覆盖率,如果某个函数是0%,那说明测试用例根本没调用它。再看分支覆盖率,这里最容易漏掉边界条件。

3.4 常见问题排查

做覆盖率分析这么多年,我踩过的坑能写一本书。这里挑几个高频问题说说。

3.4.1 .gcda文件没生成

原因: 程序异常退出、被kill、或者没有写权限。

排查: 先确认程序正常跑完了。可以用 echo $? 检查退出码。如果程序是被 SIGKILL 杀掉的,Gcov根本来不及写数据。

我曾经踩过的坑: 有一次在CI流水线里跑覆盖率,发现.gcda文件总是空的。排查了半天,发现是Docker容器退出时被强制kill了,数据根本没落盘。后来加了trap命令,确保程序正常退出。

3.4.2 报告显示全是 #####

原因: .gcno和.gcda版本不匹配,或者编译后重新链接了。

排查: 重新编译一遍,重新跑测试。记住:编译和运行必须在同一份代码上,不能改了代码不重新编译。

3.4.3 报告里出现 ??? 或乱码

原因: 源文件路径变了,或者.gcno文件损坏。

排查: 用 gcov -o <path> test.c 指定正确的路径。如果还不行,删掉所有.gcno和.gcda,重新来一遍。

3.4.4 分支覆盖率总是偏低

这个其实不是工具的问题,是测试用例设计的问题。你想想看,if-else语句有两条分支,你只测了true分支,false分支自然就是0%。

我的建议是:写测试用例时,对着代码画个控制流图,确保每个分支都走到。特别是边界条件——比如 if (x > 0),你至少要测 x=1 和 x=0 两种情况。

3.5 本章知识体系

下面这张图,是我自己总结的Gcov实战流程,你照着走基本不会出错:

Gcov实战流程 步骤1:编译 gcc -fprofile-arcs -ftest-coverage .gcno文件 可执行文件 步骤2:运行程序 ./test_program .gcda文件(动态数据) 步骤3:生成报告 gcov -b -f test.c .gcov报告文件 步骤4:解读报告 行覆盖率 / 分支覆盖率 / 函数覆盖率 步骤5:常见问题排查 .gcda未生成 / 报告全##### / 分支偏低 补充测试用例 核心原则:编译 → 运行 → 生成报告 → 解读 → 补充用例 → 重新跑

嗯,这张图把整个流程串起来了。你从编译开始,拿到.gcno和可执行文件;然后运行程序,生成.gcda;接着用gcov命令生成报告;最后解读报告,发现问题就补充测试用例,再重新跑一遍。循环往复,直到覆盖率达标。

我的经验: 别指望一次跑完所有用例。我一般先跑核心功能,看覆盖率到了多少,再针对未覆盖的代码写补充用例。这样效率最高,不会在无关紧要的代码上浪费时间。

好了,这一章的内容就到这里。你照着上面的步骤走一遍,基本就能掌握Gcov的实战用法了。遇到问题别慌,先检查.gcda有没有生成,再看报告里的 ##### 集中在哪些行,对症下药就行。

可可精品资料库