为什么 GCOV 声称这条线路没有被覆盖?

Fil*_*ipp 2 c++ code-coverage g++ gcov gcovr

我最近升级到了新版本的 gcc/g++/gcov,现在 gcov 的行为很奇怪。新版本声称未涵盖旧版本中涵盖的某些代码行。我设法将我的代码减少到这个最小的示例。

#include <memory>
using namespace std;
struct S {};

int main() {
    unique_ptr<S> s;
    s = make_unique<S>();
}
Run Code Online (Sandbox Code Playgroud)

然后我使用 编译此文件g++ -O0 -Wall -Wextra -Werror --std=c++17 --coverage,运行结果a.out,然后运行gcov

生成的.gcov文件包含:

    -:    0:Runs:1
    -:    1:#include <memory>
    -:    2:using namespace std;
    -:    3:struct S {};
    -:    4:
    1:    5:int main() {
#####:    6:    unique_ptr<S> s;
    1:    7:    s = make_unique<S>();
    1:    8:}
Run Code Online (Sandbox Code Playgroud)

这与 gcov 的旧版本不同,旧版本声称第 6 行被命中了 2 次。

为什么 gcov 认为第 6 行没有被覆盖?难道我做错了什么?

我能够使用 gcc8、gcc9 和 gcc10 重现此行为。gcc7 的行为符合预期。

比较 gcc7/8 的编译器资源管理器:https ://godbolt.org/z/Te57s4WK8

amo*_*mon 5

析构函数。通过更新到最新的 GCC 或使用其他工具来过滤覆盖范围来修复。

\n

两个编译器生成的程序集都差不多相同,但此处的程序集并不是完整的情况。相关部分是 gcov 如何将部分汇编代码与源代码关联起来。Gcov 不为此使用调试信息。相反,gcov 会在输入基本块时使用代码来增加计数器。块是没有内部控制流的汇编代码的一部分。Gcov 使用其 gcno 文件将计数器 ID 与部分源代码相关联。

\n

两个编译器版本(GCC 7 和 8)都会生成与以下内容相关的三个汇编片段:std::unique_ptr<S> s;

\n
    \n
  • 初始化变量
  • \n
  • 析构函数调用 1(正常退出)
  • \n
  • 析构函数调用 2(用于因异常而展开)
  • \n
\n

现在,前两个片段没有形成自己的块。它们是包含周围行代码的块的一部分。特别是,gcov 计数器的代码在调试信息中归因于其他行。只有异常处理程序行的块包含一个明确归属于相关行的计数器。因此,不同的 GCC 版本表现出不同程度的混乱似乎是可以理解的。由于 gcov 没有将未覆盖的代码标记为仅异常,因此混乱似乎更大。

\n

最终,实际上不可能弄清楚 gcov 是什么 \xe2\x80\x9cthinking\xe2\x80\x9d。根据我的经验,异常是 GCC 良好代码覆盖率的祸根,因为异常需要额外的代码路径。在某些情况下,可以使用以下命令进行编译-fno-exceptions. 这将使您获得完美的代码覆盖率,但它会显着改变语言。我不喜欢这个。

\n

使用 GCC 11 编译似乎可以解决您的问题。与 GCC 8、9 或 10 相比,它生成不同的程序集。覆盖率报告将显示所有被覆盖的行,并正确地注意到一个块未被覆盖(如果您使用gcov -a)。

\n

如果您无法切换到更新的 GCC,您可以考虑使用第三方工具将此类行排除在覆盖范围之外。例如 gcovr 和 lcov 让您可以使用排除标记来注释这些行,例如

\n
std::unique_ptr<S> s;  // LCOV_EXCL_LINE\n
Run Code Online (Sandbox Code Playgroud)\n

Gcovr 还允许您定义自定义正则表达式。例如,您可以使用:

\n
gcovr --exclude-lines-by-pattern \'(?x) ^ \\s* unique_ptr<.*> \\s* \\w+; $\'\n
Run Code Online (Sandbox Code Playgroud)\n