1

我最近升级到新版本的 gcc/g++/gcov,现在 gcov 的行为很奇怪。新版本声称某些代码行没有被旧版本覆盖。我设法将我的代码简化为这个最小的例子。

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

int main() {
    unique_ptr<S> s;
    s = make_unique<S>();
}

然后我使用 编译这个文件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:}

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

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

我可以用 gcc8、gcc9 和 gcc10 重现这种行为。gcc7 的行为符合预期。

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

4

1 回答 1

3

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

两个编译器生成的程序集几乎相同,但这里的程序集并不是全部。相关部分是 gcov 如何将部分汇编代码与源代码相关联。Gcov 不为此使用调试信息。相反,gcov 检测代码以在输入基本块时递增计数器。块是没有内部控制流的汇编代码的一部分。Gcov 使用其 gcno 文件将计数器 ID 与部分源代码相关联。

两个编译器版本(GCC 7 和 8)都会生成三个与声明相关的程序集片段std::unique_ptr<S> s;

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

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

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

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

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

std::unique_ptr<S> s;  // LCOV_EXCL_LINE

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

gcovr --exclude-lines-by-pattern '(?x) ^ \s* unique_ptr<.*> \s* \w+; $'
于 2021-12-30T00:24:10.107 回答