2011-08-18 6 views
23

Ho avuto questa conversazione con un collega, e si è rivelato interessante. Dire che abbiamo la seguente classe PODPerché può essere pericoloso utilizzare questa struttura POD come classe base?

struct A { 
    void clear() { memset(this, 0, sizeof(A)); } 

    int age; 
    char type; 
}; 

clear è destinato a cancellare tutti i membri, impostazione 0 (byte saggio). Cosa potrebbe andare storto se usiamo A come classe base? C'è una fonte sottile di bug qui.

+0

Spero che ci forniate la vostra risposta, Johannes. –

+0

@Fred la risposta è già stata data sotto. Quindi non c'è bisogno che io lo "scateni". –

risposta

17

È probabile che il compilatore aggiunga i byte di riempimento a A. Quindi sizeof(A) si estende oltre char type (fino alla fine del riempimento). Tuttavia in caso di ereditarietà il compilatore potrebbe non aggiungere i byte imbottiti. Quindi la chiamata a memset sovrascriverà parte della sottoclasse.

+0

@Luchian Non capisco il tuo punto? Penso che 'sizeof (int) + sizeof (char) = 5' che non è allineato su un'architettura a 32 bit .. – StackedCrooked

+0

@Luchian: Cosa pensi che' sizeof (A) 'sarebbe? La risposta potrebbe sorprendervi. –

+0

@Fred Larson sizeof (A) dipende dalla sizeof (int) sulla sua piattaforma. E ancora, in questo caso, è sizeof (int) + sizeof (char). Se l'ordine dei membri fosse invertito, sarebbe sizeof (char) + int_padding - 1 + sizeof (int). –

4

In aggiunta alle altre note, sizeof è un operatore in fase di compilazione, quindi clear() non azzererà alcun membro aggiunto da classi derivate (eccetto come notato a causa di stranezze di riempimento).

Non c'è niente di "sottile" in questo; memset è una cosa orribile da usare in C++. Nei rari casi in cui puoi semplicemente riempire la memoria di zeri e aspettarti un comportamento corretto, e hai davvero bisogno di riempire la memoria con zeri, e azzerando tutto tramite l'elenco di inizializzazione il modo civilizzato è in qualche modo inaccettabile, usa std::fill invece.

+0

I non vedo come 'std :: fill' possa essere d'aiuto in questo caso particolare - quale iteratore useresti? – Voo

+0

'char * begin = reinterpret_cast (questo); char * end = begin + sizeof (this); std :: fill (begin, end, 0) ;, credo che sia la traduzione idiomatica, ma ha tutti gli stessi problemi in realtà. Onestamente, probabilmente preferirei semplicemente creare wrapper di inizializzazione automatica per i tipi primitivi e comporre le strutture da essi. Ma il punto è che 'std :: fill' è uno strumento più sofisticato per riempire la memoria e non ci costa nulla, con un compilatore decente, nei casi in cui il semplice' memset' funzionerebbe. –

+0

@Karl: "* Onestamente probabilmente preferirei creare solo wrapper di inizializzazione automatica per i tipi primitivi *" Vedi [Boost.ValueInitialized] (http://www.boost.org/doc/libs/release/libs/utility /value_init.htm). – ildjarn

2

In teoria, il compilatore può definire le classi di base in modo diverso. C++ 03 § 10 paragrafo 5 dice:

Un sottooggetto di classe base potrebbe avere un layout (3.7) diverso dal layout di un oggetto più derivato dello stesso tipo.

Come StackedCrooked mentioned, questo potrebbe accadere dal compilatore aggiungendo padding alla fine della classe base A quando esiste come il proprio oggetto, ma il compilatore non potrebbe aggiungere che padding quando si tratta di una classe di base. Ciò farebbe sì che A::clear() sovrascriva i primi byte dei membri della sottoclasse.

Tuttavia, in pratica, non sono stato in grado di ottenere questo accada sia con GCC o Visual Studio 2008. Usando questo test:

struct A 
{ 
    void clear() { memset(this, 0, sizeof(A)); } 

    int age; 
    char type; 
}; 

struct B : public A 
{ 
    char x; 
}; 

int main(void) 
{ 
    B b; 
    printf("%d %d %d\n", sizeof(A), sizeof(B), ((char*)&b.x - (char*)&b)); 
    b.x = 3; 
    b.clear(); 
    printf("%d\n", b.x); 

    return 0; 
} 

E la modifica A, B, o entrambi per essere 'confezionato' (con #pragma pack in VS e __attribute__((packed)) in GCC), non è stato possibile ottenere in qualsiasi caso lo b.x sovrascritto. Le ottimizzazioni sono state abilitate. I 3 valori stampati per le dimensioni/offset erano sempre 8/12/8, 8/9/8 o 5/6/5.

+1

Ecco la mia prova. La base non è un POD: http://ideone.com/GHATf. Inizialmente non mi sono accorto che GCC si sarebbe comportato in modo diverso per i POD, quindi ho formulato la domanda in un modo che faccia risaltare il problema. Ma come dici tu, la possibilità esiste ancora. E scommetto che molte persone penseranno che uno sciocco "privato" non ha alcun effetto. Vale anche la pena di notare 3.9p3 e 3.9p2 dove contiene le osservazioni "... dove né obj1 né obj2 sono un suboject di classe base ..." per supportare i diversi layout. –

0

Il metodo clear della classe base imposterà solo i valori dei membri della classe.

In base alle regole di allineamento, al compilatore è consentito inserire il riempimento in modo che il prossimo membro dati si verifichi sul limite allineato. Di conseguenza, verrà eseguito il padding dopo il membro di dati type. Il primo membro dei dati del discendente occuperà questo spazio e sarà libero dagli effetti di memset, poiché la classe base non include la dimensione del discendente. Dimensione del genitore! = Dimensione del figlio (a meno che il bambino non abbia membri di dati). Vedere slicing.

L'imballaggio di strutture non fa parte dello standard di lingua. Si spera che, con un buon compilatore, la dimensione di una struttura compressa non includa byte aggiuntivi dopo l'ultimo. Anche così, un discendente impacchettato che eredita da un genitore compresso dovrebbe produrre lo stesso risultato: il genitore imposta solo i membri dei dati nel genitore.

0

Brevemente: A me sembra che l'unico problema potentional è che non riesco a trovare alcuna informazione sulla "byte di riempimento" garanzie in C89, C2003 Standarts .... Hanno alcuni volatili di straordinario o di sola lettura comportamento - non riesco a trovare nemmeno cosa vuol termine "byte di riempimento" intendiamo con lo standarts ...

dettagliata:

Per gli oggetti di tipo POD è garantita dal C++ 2003 standard che:

  • quando si memcpy il contenuto del vostro oggetto in un array di char o unsigned char, e poi memcpy il contenuto nuovamente dentro il vostro oggetto, l'oggetto terrà il suo valore originario
  • garantito che non ci sarà alcuna imbottitura in principio di un oggetto POD

  • può rompere le regole C++ su: istruzione goto, durata

Per C89 c'è anche esistono alcune garanzie sulle strutture:

  • Quando viene utilizzato per una miscela di strutture sindacali se struct hanno lo stesso inizio, quindi primi compoments hanno perfetta mathing

  • strutture sizeof in C è pari alla quantità di memoria per memorizzare tutti i componenti, posto sotto la imbottitura tra i componenti, inserire imbottitura sotto le seguenti strutture

  • In componenti C della struttura sono indicati gli indirizzi. È garantito che i componenti dell'indirizzo sono in ordine crescente. E l'indirizzo del primo componente coincide con l'indirizzo iniziale della struttura. Indipendentemente da quale Endian computer in cui il programma viene eseguito

così sembra a me che tali norme è opportuno C++ anche, e tutto va bene. Penso davvero che a livello di hardware nessuno ti limiti a scrivere byte padding per oggetti non-const.

Problemi correlati