4、GCC与Gcov入门:安装GCC、理解Gcov工作原理、编译带覆盖率信息的程序

好,咱们正式开始动手了。这一章,我带你搞定最基础的工具链——GCC和Gcov。说白了,GCC负责编译,Gcov负责告诉你哪些代码跑过了、哪些没跑过。这两个东西是覆盖率分析的基石,绕不开的。

4.1 安装GCC:别小看这一步

我个人习惯用Linux环境做覆盖率分析,Windows上虽然也能折腾,但坑多。如果你用的是Ubuntu或Debian系,安装GCC就一行命令:

sudo apt-get update
sudo apt-get install gcc g++ gcov

嗯,这里要注意:Gcov是GCC自带的,不需要单独装。你装好GCC,Gcov就有了。验证一下:

gcc --version
gcov --version

看到版本号输出,就说明环境搭好了。我在项目中遇到过有人装了半天发现gcov命令找不到,结果是因为只装了gcc没装g++——其实gcov是跟着gcc一起的,但有些发行版会拆成多个包。建议你直接装build-essential这个元包,省心:

sudo apt-get install build-essential
小提示:如果你用CentOS或RHEL,命令是 yum install gcc gcc-c++。macOS用户可以用Homebrew装 brew install gcc,但注意macOS自带的clang和GCC不是一回事,别搞混了。

4.2 Gcov工作原理:它到底在干嘛?

Gcov的工作原理,说白了就三步:

  1. 编译时插桩——GCC在编译时往你的代码里插入一些“计数器”。每执行一行代码,对应的计数器就加1。
  2. 运行时收集——程序跑起来,这些计数器默默记录执行次数。
  3. 事后分析——程序退出后,Gcov读取计数器数据,生成报告。

为什么会这样设计?你想想看,如果等程序跑完了再去分析,根本不知道哪些分支走了、哪些没走。插桩就像在代码里装了个行车记录仪,每经过一个路口就拍一张照。

我刚开始接触Gcov时,以为它是在运行时动态分析的,后来才发现它是在编译阶段就埋好了“探针”。这个理解很重要——它决定了你必须在编译时就开启覆盖率选项,否则后面啥也拿不到。

核心要点:Gcov的插桩是在编译阶段完成的,不是运行时。所以编译命令里必须加 -fprofile-arcs -ftest-coverage 这两个选项。

4.3 编译带覆盖率信息的程序

好,理论说完了,咱们直接上手。写一个简单的C程序,就叫 test.c:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 10;
    int y = 20;
    int z = add(x, y);
    printf("z = %d\n", z);
    return 0;
}

编译时加上覆盖率选项:

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

这条命令会生成两个额外文件:

  • test.gcno——这是编译时生成的,记录了代码的块结构和行号信息。说白了就是“地图”。
  • test.gcda——这是运行时生成的,记录了实际的执行次数。程序跑完才会出现。

然后运行程序:

./test

这时候你会看到 test.gcda 文件出现了。最后用Gcov分析:

gcov test.c

它会生成一个 test.c.gcov 文件,打开看看:

        -:    0:Source:test.c
        -:    0:Graph:test.gcno
        -:    0:Data:test.gcda
        -:    0:Runs:1
        -:    1:#include <stdio.h>
        -:    2:
        -:    3:int add(int a, int b) {
        1:    4:    return a + b;
        -:    5:}
        -:    6:
        1:    7:int main() {
        -:    8:    int x = 10;
        -:    9:    int y = 20;
        1:   10:    int z = add(x, y);
        1:   11:    printf("z = %d\n", z);
        1:   12:    return 0;
        -:   13:}

看到左边的数字了吗?那就是每行代码的执行次数。第4行显示 1,说明 add 函数被调用了1次。如果某行显示 #####,表示这行代码从未被执行过——这就是覆盖率分析的起点。

注意:Gcov默认只分析当前目录下的源文件。如果你的源文件在子目录里,需要用 -o 参数指定路径。我曾经因为没注意这个,折腾了半天才发现报告是空的。

4.4 一张图看懂GCC+Gcov工作流

下面这张SVG图,把整个流程串起来了。你一看就明白:

GCC + Gcov 覆盖率分析工作流 源文件 test.c 编译 GCC 编译 -fprofile-arcs -ftest-coverage 可执行文件 test test.gcno (地图) 运行 运行程序 ./test test.gcda 执行计数数据 分析 Gcov 分析 gcov test.c 覆盖率报告 test.c.gcov 关键文件:.gcno(编译时生成) + .gcda(运行时生成) → 合并分析

4.5 避坑指南:我踩过的几个坑

讲几个我实际遇到过的坑,你遇到了能少走弯路:

  • 坑一:忘记加编译选项——编译时没加 -fprofile-arcs -ftest-coverage,结果gcov报错说找不到.gcno文件。这个我犯过不止一次,后来养成了习惯:编译命令写进Makefile,用变量控制。
  • 坑二:多文件项目只编译了一个文件——如果你有多个.c文件,每个都要加编译选项。只加一个的话,其他文件的覆盖率数据是空的。
  • 坑三:程序没正常退出——Gcov的数据是在程序退出时刷盘的。如果程序被kill -9强杀,.gcda文件可能不完整。我曾经跑一个长时间的服务,忘了处理退出信号,结果覆盖率数据全丢了。
  • 坑四:优化选项干扰——GCC的优化(比如-O2)会改变代码结构,导致行号对应不上。建议分析覆盖率时用 -O0 关闭优化。
我的习惯:在Makefile里单独定义一个 coverage 目标,专门用来编译带覆盖率信息的版本。这样既不影响正常编译,又能随时切到覆盖率模式。具体写法后面章节会讲。

好了,这一章的内容就这些。你装好GCC,跑通上面的例子,就算入门了。下一章咱们聊聊怎么用Gcov分析分支覆盖率和函数覆盖率——那才是真正有用的东西。


可可精品资料库