C++ - 对结构使用GTest值参数化测试会导致valgrind错误

dav*_*idA 6 c++ struct valgrind packing googletest

你能帮我理解GTest和struct packing的内容吗?

问题似乎与在GTest中作为值参数化测试中的值使用时如何打包结构有关.采用直接的方法为每个值实例化一个结构会导致与未初始化值相关的valgrind错误.

这是有关的代码:

#include <gtest/gtest.h>

struct TestItem
{
    const char * aString;
    int anInt0;
    int anInt1;
    int anInt2;
};

class TestBase : public ::testing::Test, public ::testing::WithParamInterface<TestItem> {};

TEST_P(TestBase, TestAtoi)
{
    TestItem item = GetParam();
    std::cout << sizeof(TestItem) << std::endl;
    ASSERT_FALSE(0);   // actual test doesn't matter
}

INSTANTIATE_TEST_CASE_P(
        TestBaseInstantiation,
        TestBase,
        ::testing::Values(
                TestItem { "0", 0, 0, 0 }
));
Run Code Online (Sandbox Code Playgroud)

当编译和链接时(我使用cmake构建GTest):

g++ gtest_valgrind.c -o gtest_valgrind -I gtest-1.7.0/include -L gtest-1.7.0/build -lgtest -lpthread -lgtest_main -std=c++11
Run Code Online (Sandbox Code Playgroud)

然后执行:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./gtest_valgrind
Run Code Online (Sandbox Code Playgroud)

生成以下输出:

==17290== Memcheck, a memory error detector
==17290== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==17290== Using Valgrind-3.10.0.SVN and LibVEX; rerun with -h for copyright info
==17290== Command: ./gtest_valgrind
==17290== 
Running main() from gtest_main.cc
==17290== Use of uninitialised value of size 8
==17290==    at 0x55B89F1: _itoa_word (_itoa.c:180)
==17290==    by 0x55BC6F6: vfprintf (vfprintf.c:1660)
==17290==    by 0x55E1578: vsnprintf (vsnprintf.c:119)
==17290==    by 0x55C3531: snprintf (snprintf.c:33)
==17290==    by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==  Uninitialised value was created by a stack allocation
==17290==    at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290== 
==17290== Conditional jump or move depends on uninitialised value(s)
==17290==    at 0x55B89F8: _itoa_word (_itoa.c:180)
==17290==    by 0x55BC6F6: vfprintf (vfprintf.c:1660)
==17290==    by 0x55E1578: vsnprintf (vsnprintf.c:119)
==17290==    by 0x55C3531: snprintf (snprintf.c:33)
==17290==    by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==  Uninitialised value was created by a stack allocation
==17290==    at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290== 
==17290== Conditional jump or move depends on uninitialised value(s)
==17290==    at 0x55BC742: vfprintf (vfprintf.c:1660)
==17290==    by 0x55E1578: vsnprintf (vsnprintf.c:119)
==17290==    by 0x55C3531: snprintf (snprintf.c:33)
==17290==    by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==  Uninitialised value was created by a stack allocation
==17290==    at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290== 
==17290== Conditional jump or move depends on uninitialised value(s)
==17290==    at 0x55B9659: vfprintf (vfprintf.c:1660)
==17290==    by 0x55E1578: vsnprintf (vsnprintf.c:119)
==17290==    by 0x55C3531: snprintf (snprintf.c:33)
==17290==    by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==  Uninitialised value was created by a stack allocation
==17290==    at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290== 
==17290== Conditional jump or move depends on uninitialised value(s)
==17290==    at 0x55B96DC: vfprintf (vfprintf.c:1660)
==17290==    by 0x55E1578: vsnprintf (vsnprintf.c:119)
==17290==    by 0x55C3531: snprintf (snprintf.c:33)
==17290==    by 0x41F107: testing::(anonymous namespace)::PrintByteSegmentInObjectTo(unsigned char const*, unsigned long, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F1AA: testing::(anonymous namespace)::PrintBytesInObjectToImpl(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x41F252: testing::internal2::PrintBytesInObjectTo(unsigned char const*, unsigned long, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B8E: testing::internal2::TypeWithoutFormatter<TestItem, (testing::internal2::TypeKind)2>::PrintValue(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B63: std::basic_ostream<char, std::char_traits<char> >& testing::internal2::operator<< <char, std::char_traits<char>, TestItem>(std::basic_ostream<char, std::char_traits<char> >&, TestItem const&) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B3E: void testing_internal::DefaultPrintNonContainerTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409B19: void testing::internal::DefaultPrintTo<TestItem>(char, testing::internal::bool_constant<false>, TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409ADB: void testing::internal::PrintTo<TestItem>(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==    by 0x409AA6: testing::internal::UniversalPrinter<TestItem>::Print(TestItem const&, std::ostream*) (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290==  Uninitialised value was created by a stack allocation
==17290==    at 0x404AAE: gtest_TestBaseInstantiationTestBase_EvalGenerator_() (in /home/davida/git/gitlab/fcdm/lwm2m.git/gtest_valgrind)
==17290== 
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from TestBaseInstantiation/TestBase
[ RUN      ] TestBaseInstantiation/TestBase.TestAtoi/0
24
[       OK ] TestBaseInstantiation/TestBase.TestAtoi/0 (76 ms)
[----------] 1 test from TestBaseInstantiation/TestBase (126 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (246 ms total)
[  PASSED  ] 1 test.
==17290== 
==17290== HEAP SUMMARY:
==17290==     in use at exit: 0 bytes in 0 blocks
==17290==   total heap usage: 239 allocs, 239 frees, 46,622 bytes allocated
==17290== 
==17290== All heap blocks were freed -- no leaks are possible
==17290== 
==17290== For counts of detected and suppressed errors, rerun with: -v
==17290== ERROR SUMMARY: 20 errors from 5 contexts (suppressed: 0 from 0)
Run Code Online (Sandbox Code Playgroud)

这是很多输出,但基本上我认为它表明由于TestItem { "0", 0, 0, 0 }行中的实例化而导致的结构有些不同寻常INSTANTIATE_TEST_CASE_P.

另请注意,TestItem的大小输出为24.在64位系统上,我不太确定如何协调它.8个char *字节用于int成员,4*3 = 12个字节用于成员.如果它们与8字节边界对齐,则应该是24 + 4 = 28字节(或者如果包括打包则为32).如果它们与4个字节的边界对齐,那么这应该是20.这里有一些关于结构打包我不明白的东西.

可以通过不同地打包结构来消除valgrind警告.例如,所有这三个修改都会导致干净的valgrind运行:

使用#pragma pack:

#pragma pack(1)
struct TestItem
{
    const char * aString;
    int anInt0;
    int anInt1;
    int anInt2;
};
Run Code Online (Sandbox Code Playgroud)

那输出20.

使用GNU g ++打包属性:

struct TestItem
{
    const char * aString;
    int anInt0;
    int anInt1;
    int anInt2;
} __attribute__((packed));
Run Code Online (Sandbox Code Playgroud)

那也是输出20.

最后,向结构添加一个虚拟值:

struct TestItem
{
    const char * aString;
    int anInt0;
    int anInt1;
    int anInt2;
    int anInt3;   // add an extra member
};
Run Code Online (Sandbox Code Playgroud)

那输出24.

我有相当数量的项目代码使用这种精确的技术进行价值参数化测试,并且在所有情况下,如果不使用其中一种避免策略,valgrind会抱怨.

我想知道我为这个测试创建值的方法是否存在根本性的错误,或者这是gtest实例化测试用例的结果?或者,最不可能的是,这是一个gtest bug?

Ant*_*rez 5

的输出sizeofTestItem结构是编译器的结果结构对准和后填充.

从上面的链接:

[...]它是结构数据后面的第一个地址,与结构具有相同的对齐方式.

尾随结构填充的一般规则是:编译器的行为就像结构具有尾随填充到其步幅地址一样.此规则控制sizeof()将返回的内容.

在64位x86或ARM机器上考虑此示例:

struct foo3 {
    char *p;     /* 8 bytes */
    char c;      /* 1 byte */
};
struct foo3 singleton;
struct foo3 quad[4];
Run Code Online (Sandbox Code Playgroud)

你可能认为sizeof(struct foo3)应该是9,但它实际上是16.步幅地址是(&p)[2].因此,在四元组数组中,每个成员具有7个字节的尾随填充,因为每个后续结构的第一个成员想要在8字节边界上自对齐.内存布局就好像结构声明如下:

struct foo3 {
     char *p;     /* 8 bytes */
     char c;      /* 1 byte */
     char pad[7];
};
Run Code Online (Sandbox Code Playgroud)

这就解释了为什么你得到24,sizeof(TestItem)因为尾随填充将结构与多个结果对齐sizeof (const char*),即8.

这个尾随填充字节是未初始化的,这是valgrind报告的内容.Gtest正在运行一些代码,以便TestItem在测试失败时打印参数的实际值.如果您进行测试并且valgrind没有显示错误,则可以确认这一点.

当您强制编译器使用特定对齐或向结构添加新成员以使结构不需要任何尾部填充时,valgrind在TestItem实例中找不到任何未初始化的字节.

Gtest通常operator<<在打印值时调用,如果不可用则返回打印对象中的原始字节数组.这可能是访问未初始化的尾随填充字节的原因.所以你也可以通过定义operator<<for 来摆脱valgrind错误TestItem.