2015-11-17 40 views
5

Puoi aiutarmi a capire cosa sta succedendo qui con GTest e struct packing?C++: l'uso dei test con parametri GTest con struct causa errori valgrind

Il problema sembra essere correlato al modo in cui una struct viene impacchettata quando viene utilizzata come valore in un test con parametri parametrizzati in GTest. L'approccio diretto di istanziare una struttura per ogni valore determina errori valgrind relativi a valori non inizializzati.

Ecco il codice in questione:

#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 } 
)); 

Quando questo viene compilato e collegato (ho costruito GTEST con 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 

e quindi eseguito con:

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

Il viene prodotto l'output seguente:

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

Questo è un sacco di output, ma in sostanza penso che sta indicando che c'è qualcosa di insolito nella struttura che viene istanziata a causa della linea TestItem { "0", 0, 0, 0 } in INSTANTIATE_TEST_CASE_P.

Si noti inoltre che la dimensione di TestItem viene emessa come 24. Su un sistema a 64 bit, non sono abbastanza sicuro di come riconciliare questo. 8 byte per char * e 4 * 3 = 12 byte per i membri int. Se sono allineati ai limiti di 8 byte, questa deve essere la dimensione 24 + 4 = 28 byte (o 32 se include l'imballaggio). Se sono allineati ai limiti di 4 byte, allora questo dovrebbe essere 20. C'è qualcosa che non capisco qui su struct packing.

L'avviso valgrind può essere eliminato imballando la struttura in modo diverso. Ad esempio, tutti e tre tali modifiche determinino una corsa valgrind pulito:

Uso #pragma pacchetto:

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

che emette 20.

Uso GNU g ++ attributo imballaggio:

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

che emette 20 troppo.

Infine, aggiungendo un valore fittizia alla struct:

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

che emette 24.

Ho una discreta quantità di codice di progetto che utilizza questa tecnica esatta per i test con parametri parametrici, e in tutti i casi valgrind si lamenterà se una delle strategie di elusione non viene utilizzata.

Mi piacerebbe sapere se c'è qualcosa di fondamentalmente sbagliato nel mio approccio alla creazione di valori per questo test, o è una conseguenza di qualcosa di più dei casi di test di istanziazione? O, molto improbabile, questo è un bug gtest?

risposta

5

L'output di sizeof per la struttura TestItem è una conseguenza del compilatore structure alignment and trailing padding.

Dal link sopra:

[...] E 'il primo indirizzo seguendo la struttura dati che ha lo stesso allineamento come struttura.

La regola generale del riempimento della struttura finale è la seguente: il compilatore si comporterà come se la struttura avesse il trascinamento del riempimento verso l'indirizzo dello stride . Questa regola controlla ciò che sizeof() restituirà.

Considerate questo esempio su un x86 o ARM computer a 64 bit:

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

Si potrebbe pensare che sizeof(struct foo3) dovrebbe essere 9, ma in realtà è 16. L'indirizzo passo è quello di (& p) [ 2]. Pertanto, nell'array quad, ogni membro ha 7 byte di riempimento finale, poiché il primo membro di ciascuna seguente struttura desidera essere autoallineato su un limite di 8 byte. Il layout di memoria è come se la struttura era stata dichiarata in questo modo:

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

Questo spiega il motivo per cui si stanno ottenendo 24 per sizeof(TestItem) poiché l'imbottitura posteriore allineerà la struttura ad un multiplo di sizeof (const char*), che è 8

Questi byte di riempimento finale non sono inizializzati e questo è ciò che Valgrind segnala. Gtest sta eseguendo del codice per stampare il valore effettivo del parametro TestItem quando un test fallisce. Questo potrebbe essere confermato se si effettua il test e Valgrind non mostra l'errore.

Quando si forza il compilatore a utilizzare un allineamento specifico o si aggiunge un nuovo membro alla struttura in modo che la struttura non abbia bisogno di alcun riempimento finale, valgrind non trova byte non inizializzati nelle istanze TestItem.

Gtest di solito chiama operator<< quando stampa i valori e ricade per stampare la matrice raw di byte nell'oggetto se non è disponibile. Questo è probabilmente il motivo per cui si accede ai byte di padding non inizializzati. Quindi puoi anche eliminare gli errori valgrind definendo operator<< per TestItem.

+0

Grazie, ha senso. Comunque il test sta passando - 'ASSERT_FALSE (0)' riuscirà, quindi non viene stampato nessun dump dei dati. Ma penso che il tuo punto sia ancora valido: il framework di test deve eseguire la scansione dei dati in base a 'sizeof' e leggendo i valori non inizializzati nel padding. – meowsqueak

+0

@meowsqueak Quindi Gtest sta tentando di stampare il valore del parametro di test anche quando il test è superato. È strano, perché non puoi vedere il valore reale stampato in output standard, giusto? Hai provato a definire l'operatore << '? –

+1

Sì, penso che stia facendo almeno un snprintf sul valore, anche se non lo stampa. Forse prepara l'output in modo che la funzione che esegue effettivamente il test possa facilmente stampare la descrizione precotta, se necessario. Ho la fonte, potrei scoprire se ne avessi bisogno. La tua idea di definire l'operatore '' funziona come previsto ed elimina l'errore valgrind. È molto più utile di un buffer dump comunque, quindi probabilmente lo userò per tutto il progetto. – meowsqueak