2015-11-17 52 views
5

GTestと構造体のパッキングで何が起こっているのか理解できますか?C++ - 構造体でGTest値をパラメータ化したテストを使用すると、valgrindエラーが発生する

問題は、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 } 
)); 

これは、コンパイルとリンク(私は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 

そしてで実行:

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ビットシステムでは、これをどのように調整するのかよく分かりません。 char *の場合は8バイト、intのメンバーの場合は4 * 3 = 12バイトです。それらが8バイトの境界に整列されている場合、これはサイズ24 + 4 = 28バイト(またはパッキングを含む場合は32)でなければなりません。もしそれらが4バイトの境界に揃えられていれば、20になるはずです。構造体パッキングについてここでは理解できないことがあります。

valgrindの警告は、構造体を別の方法でパックすることで取り除くことができます。例えば、これらの変更のすべての3つは、きれいなvalgrindの実行につながる:

の#pragma packを使用して:

#pragma pack(1) 
struct TestItem 
{ 
    const char * aString; 
    int anInt0; 
    int anInt1; 
    int anInt2; 
}; 

20を出力します。

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

すぎ20を出力:属性を梱包GNU G ++を使用して

。構造体にダミー値を追加

最後に、:

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

24を出力します。

私は、この正確な手法をバリューパラメータテストに使用するかなりの量のプロジェクトコードを持っていますが、回避戦略の1つが使用されていないと、valgrindはすべての場合に不平を言います。

私は、このテストの値を作成する私のアプローチに根本的に間違っているものがあるのか​​、それともgtestがテストケースをインスタンス化することの結果であることを知りたいのですか?あるいは、ほとんどありそうもない、これはgtestのバグですか?

答えて

5

TestItem構造の出力は、structure alignment and trailing paddingです。上記リンクから

[...]これは、構造として同じ 配向を有する構造データに続く最初のアドレスです。

末尾の構造体パディングの一般的な規則は次のとおりです。コンパイラ は、構造体の後ろに余分なパディングがあるため、そのストライドアドレスが になります。このルールは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であることを考えるかもしれないが、それはストライド アドレスは(& P)のものである、実際に16です2]。したがって、クワッド配列では、各メンバーは、 構造体の最初のメンバーが8バイト境界で自己整列する必要があるため、7 バイトの末尾のパディングを持ちます。末尾のパディングが8である、sizeof (const char*)の倍数に構造を揃えますので、あなたがsizeof(TestItem)のために24を取得している理由を説明すること

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

:構造はこのように宣言されていたかのようにメモリ レイアウトがあります

この末尾のパディングバイトは初期化されておらず、これはvalgrindがレポートするものです。テストが失敗したときにTestItemパラメータの実際の値を出力するために、Gtestがいくつかのコードを実行しています。これは、テストをパスしてvalgrindがエラーを表示しない場合に確認できます。

特定の配置を使用するようにコンパイラを強制するか、構造体に後続の埋め込みが不要になるように構造体に新しいメンバーを追加すると、valgrindはTestItemインスタンスの初期化されていないバイトを検出しません。

Gtestは通常、値を印刷するときにはoperator<<を呼び出し、使用できない場合はオブジェクトの生の配列を印刷します。これはおそらく、初期化されていない末尾のパディングバイトにアクセスしている理由です。したがって、TestItemのためにoperator<<を定義することによって、valgrindエラーを取り除くこともできます。

+0

ありがとうございます。しかし、テストは成功しています。 'ASSERT_FALSE(0)'は成功し、データのダンプは出力されません。しかし、私はあなたの意見はまだ残っていると思います。テストフレームワークは、 'sizeof'に従ってデータをスキャンし、パディングで初期化されていない値を読み取る必要があります。 – meowsqueak

+0

@meowsqueak Gtestは、テストに合格してもテストパラメータの値を表示しようとしています。これは奇妙なことです。なぜなら、標準出力で実際に印刷された値を見ることができないからです。 'operator <<'を定義しようとしましたか? –

+1

はい、私はそれが印刷されていなくても、少なくとも値のsnprintfを行っていると思います。テストを実際に実行する関数が、必要に応じて事前に調理された記述を簡単に印刷できるように、出力を準備するかもしれません。私は情報源を持っています。私が必要なのかどうかを知ることができました。 'operator <<'を定義するあなたの考え方は期待どおりに機能し、valgrindエラーを排除します。とにかくバッファーダンプよりもはるかに便利なので、私はおそらくこのプロジェクト全体を通してこれを使うでしょう。 – meowsqueak

関連する問題