использование параметрических тестов GTest со структурами приводит к ошибкам valgrind

Можете ли вы помочь мне понять, что здесь происходит с GTest и структурой упаковки?

Похоже, проблема связана с тем, как структура упаковывается при использовании в качестве значения в параметризованном тесте в 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 }
));

Когда это скомпилировано и связано (я собрал GTest с помощью cmake):

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

И затем выполняется с:

valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./gtest_valgrind

Получается следующий вывод:

==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)

Это большой вывод, но, по сути, я думаю, что это указывает на то, что есть что-то необычное в структуре, которая создается из-за TestItem { "0", 0, 0, 0 } линия в INSTANTIATE_TEST_CASE_P,

Также обратите внимание, что размер TestItem выводится как 24. В 64-битной системе я не совсем уверен, как это согласовать. 8 байт для char *и 4 * 3 = 12 байт для int члены. Если они выровнены по 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;
};

Что выводит 20,

Использование атрибута упаковки GNU g ++:

struct TestItem
{
const char * aString;
int anInt0;
int anInt1;
int anInt2;
} __attribute__((packed));

Что выводит 20 тоже.

Наконец, добавление фиктивного значения в структуру:

struct TestItem
{
const char * aString;
int anInt0;
int anInt1;
int anInt2;
int anInt3;   // add an extra member
};

Что выводит 24,

У меня есть достаточно кода проекта, который использует эту точную технику для параметризованных тестов, и во всех случаях valgrind будет жаловаться, если не используется одна из стратегий избегания.

Я хотел бы знать, есть ли что-то принципиально неправильное в моем подходе к созданию значений для этого теста, или это следствие того, что gtest делает с созданием экземпляров тестовых случаев? Или, что маловероятно, это самая большая ошибка?

5

Решение

Выход из sizeof для TestItem структура является следствием компилятора выравнивание структуры и концевые отступы.

По ссылке выше:

[…] Это первый адрес после структурных данных, который имеет так же
выравнивание как структура
.

Общее правило заполнения конечной структуры таково: компилятор
будет вести себя так, как будто структура имеет конечный отступ
адрес шага. Это правило контролирует, что sizeof () вернет.

Рассмотрим этот пример на 64-битном компьютере с архитектурой x86 или ARM:

struct foo3 {
char *p;     /* 8 bytes */
char c;      /* 1 byte */
};
struct foo3 singleton;
struct foo3 quad[4];

Вы можете подумать, что sizeof(struct foo3) должно быть 9, но на самом деле это 16. Шаг
адрес является адресом (&р) [2]. Таким образом, в массиве quad каждый член имеет 7
байты конечного заполнения, потому что первый член каждого следующего
struct хочет быть выровненной на 8-байтовой границе. Память
расположение так, как если бы структура была объявлена ​​так:

struct foo3 {
char *p;     /* 8 bytes */
char c;      /* 1 byte */
char pad[7];
};

Это объясняет, почему вы получаете 24 за sizeof(TestItem) так как завершающий отступ выравнивает структуру по кратному sizeof (const char*), что составляет 8.

Эти завершающие байты заполнения неинициализированы, и именно об этом сообщает valgrind. Gtest запускает некоторый код, чтобы напечатать фактическое значение TestItem параметр, когда тест не пройден. Это может быть подтверждено, если вы пройдете тест, и valgrind не покажет ошибку.

Когда вы заставляете компилятор использовать конкретное выравнивание или добавляете новый элемент в структуру, чтобы структура не нуждалась в дополнительном заполнении, valgrind не находит никаких неинициализированных байтов в TestItem экземпляров.

Гтест обычно звонит operator<< при печати значений и возвращается к печати необработанного массива байтов в объекте, если он недоступен. Вероятно, поэтому неинициализированные байты завершающего заполнения становятся доступными. Таким образом, вы также можете избавиться от ошибок valgrind, определив operator<< за TestItem,

5

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]