Можете ли вы помочь мне понять, что здесь происходит с 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 делает с созданием экземпляров тестовых случаев? Или, что маловероятно, это самая большая ошибка?
Выход из 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
,
Других решений пока нет …