2009-11-04 8 views
14

Durante una revisione del codice che ho incontrato un po 'di codice che definisce una struttura semplice come segue:C++ dei dati Gli Allineamento e Array Imballo

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} 

Altrove, una matrice di questi oggetti è definito:

foo listOfFoos[SOME_NUM]; 

Successivamente, le strutture sono prime copiati in un buffer:

memcpy(pBuff,listOfFoos,3*SOME_NUM); 

Questo codice si basa sul assumpti ons that: a.) La dimensione di foo è 3, e nessuna padding è applicata, e b.) Una matrice di questi oggetti è imballata senza padding tra di loro.

L'ho provato con GNU su due piattaforme (RedHat 64b, Solaris 9) e ha funzionato su entrambi.

Le ipotesi sopra sono valide? In caso contrario, a quali condizioni (ad es. Cambiare OS/compilatore) potrebbero non riuscire?

+1

E qualcuno ha inventato std: vector ... –

+0

@Matthieu: Grazie per averci ricordato. Sono sicuro che il PO l'aveva ignorato. – nus

risposta

16

Una serie di oggetti deve essere contigua, quindi non c'è mai padding tra gli oggetti, anche se padding può essere aggiunto alla fine di un oggetto (producendo quasi lo stesso effetto).

Dato che si sta lavorando con char, le ipotesi sono probabilmente corrette più spesso, ma lo standard C++ di certo non lo garantisce. Un compilatore diverso, o anche solo una modifica dei flag passati al compilatore corrente potrebbe comportare l'inserimento di padding tra gli elementi della struct o seguendo l'ultimo elemento della struct, o entrambi.

+1

Certamente non mi sorprenderebbe se un compilatore decidesse di apprezzare le cose sui limiti a quattro byte e mettere un byte di padding alla fine. –

+0

Sfortunatamente la maggior parte non lo fa. – Crashworks

20

Sarebbe sicuramente più sicuro di fare:

sizeof(foo) * SOME_NUM 
+2

non solo più sicuro, ma più chiaro e si libera di un numero magico. +1 – rmeador

+0

Sì, sono d'accordo. Immagino che stavo tentando di ottenere l'imbottitura e l'organizzazione dell'array. Grazie. –

+1

tuttavia non considera il riempimento tra gli elementi dell'array. – nschmidt

2

sarei stato sicuro e sostituito il numero magico 3 con un sizeof(foo) mi sa.

La mia ipotesi è che il codice ottimizzato per le future architetture del processore introdurrà probabilmente qualche forma di padding.

E provare a rintracciare quel tipo di bug è un vero dolore!

1

Come altri hanno già detto, l'uso di sizeof (foo) è una scommessa più sicura. Alcuni compilatori (specialmente quelli esoterici nel mondo embedded) aggiungeranno un'intestazione di 4 byte alle classi. Altri possono fare trucchi funky di allineamento della memoria, a seconda delle impostazioni del compilatore.

Per una piattaforma mainstream, probabilmente stai bene, ma non è una garanzia.

5

Se si copia l'array come questo si dovrebbe usare

memcpy(pBuff,listOfFoos,sizeof(listOfFoos)); 

Questo sarà sempre funzionerà fino a quando si assegnato pBuff alla stessa dimensione. In questo modo non si fanno assolutamente ipotesi sul padding e sull'allineamento.

La maggior parte dei compilatori allinea una struttura o una classe all'allineamento richiesto del tipo più grande incluso. Nel tuo caso di caratteri che non significa allineamento e riempimento, ma se aggiungi un corto, ad esempio la tua classe sarebbe di 6 byte di grandi dimensioni con un byte di padding aggiunto tra l'ultimo char e il tuo short.

2

Tutto si riduce all'allineamento della memoria.Le tipiche macchine a 32 bit leggono o scrivono 4 byte di memoria per tentativo. Questa struttura è al riparo da problemi perché rientra in quei 4 byte facilmente senza problemi di riempimento confusionario.

Ora, se la struttura è stata in quanto tale:

class foo { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
    unsigned int i; 
    unsigned int j; 
} 

La logica colleghi sarebbe probabilmente portare a = 3 byte

memcpy(pBuff,listOfFoos,11*SOME_NUM); 

(3 di Char, 2 ints = 2 * 4 byte, quindi 3 + 8)

Sfortunatamente, a causa del riempimento, la struttura occupa in realtà 12 byte. Questo perché non è possibile inserire tre caratteri di tipo char e un int in quella parola di 4 byte, e quindi c'è un byte di spazio imbottito che spinge l'int nella propria parola. Questo diventa sempre più un problema, più i tipi di dati diventano diversi.

4

Penso che la ragione per cui questo funzioni perché tutti i campi nella struttura sono caratteri che ne allineano uno. Se c'è almeno un campo che non allinea 1, l'allineamento della struttura/classe non sarà 1 (l'allineamento dipenderà dall'ordine dei campi e dall'allineamento).

Vediamo alcuni esempi:

#include <stdio.h> 
#include <stddef.h> 

typedef struct { 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Foo; 
typedef struct { 
    unsigned short i; 
    unsigned char a; 
    unsigned char b; 
    unsigned char c; 
} Bar; 
typedef struct { Foo F[5]; } F_B; 
typedef struct { Bar B[5]; } B_F; 


#define ALIGNMENT_OF(t) offsetof(struct { char x; t test; }, test) 

int main(void) { 
    printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo)); 
    printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar)); 
    printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B)); 
    printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F)); 
} 

Quando viene eseguito, il risultato è:

Foo:: Size: 3; Alignment: 1 
Bar:: Size: 6; Alignment: 2 
F_B:: Size: 15; Alignment: 1 
B_F:: Size: 30; Alignment: 2 

Si può vedere che Bar e F_B ha allineamento 2 in modo che il suo campo mi verrà allineato correttamente. Puoi anche vedere che Dimensione della barra è 6 e non 5. Allo stesso modo, la dimensione di B_F (5 di Bar) è 30 e non 25.

Quindi, se si è un codice rigido anziché sizeof(...), si verificherà un problema qui.

Spero che questo aiuti.

+0

sembra grandioso, sfortunatamente la struttura anonima all'interno della chiamata offsetoff non viene compilata in msvc 2010 – nus

2

Per situazioni in cui vengono utilizzate cose come queste e non posso evitarlo, cerco di interrompere la compilazione quando le presunzioni non vengono più mantenute. Io uso qualcosa di simile al seguente (o Boost.StaticAssert se la situazione lo consente):

static_assert(sizeof(foo) <= 3); 

// Macro for "static-assert" (only usefull on compile-time constant expressions) 
#define static_assert(exp)   static_assert_II(exp, __LINE__) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_II(exp, line) static_assert_III(exp, line) 
// Macro used by static_assert macro (don't use directly) 
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)} 
0

Ci potrebbe essere ancora un problema con sizeof() quando si passa i dati tra due computer. Su uno di essi il codice potrebbe essere compilato con padding e nell'altro senza, nel qual caso sizeof() darebbe risultati diversi. Se i dati dell'array vengono passati da un computer all'altro, verranno erroneamente interpretati perché gli elementi dell'array non verranno trovati dove previsto. Una soluzione è assicurarsi che #pragma pack (1) sia usato quando possibile, ma potrebbe non essere sufficiente per gli array. La cosa migliore è prevedere il problema e usare il padding su un multiplo di 8 byte per elemento di array.