2016-07-12 39 views
28

Nel mio primo esempio ho due bitfield utilizzando int64_t. Quando compilo e ottenere la dimensione della classe ottengo 8.Perché le dimensioni della classe aumentano quando int64_t cambia in int32_t

class Test 
{ 
    int64_t first : 40; 
    int64_t second : 24; 
}; 

int main() 
{ 
    std::cout << sizeof(Test); // 8 
} 

Ma quando cambio la seconda bitfeild per essere un int32_t la dimensione della classe raddoppia a 16:

class Test 
{ 
    int64_t first : 40; 
    int32_t second : 24; 
}; 

int main() 
{ 
    std::cout << sizeof(Test); // 16 
} 

Questo accade su GCC 5.3.0 e MSVC 2015. Ma perché?

+11

** La dimensione ** aumenta, non l'allineamento. Nel primo caso, il primo e il secondo fanno parte dello stesso int64_t. Nel secondo caso, ovviamente non possono. –

+0

Cerca di ottenere indirizzi di campi, o anche meglio - assemblaggio di codice generato da un post che accede a entrambi i campi. O almeno - quale compilatore usi? – lorond

+0

@lorond GCC 5.3.0 – xinaiz

risposta

35

Nel primo esempio

int64_t first : 40; 
int64_t second : 24; 

Sia first e second utilizzare i 64 bit di un singolo 64 bit intero. Ciò fa sì che la dimensione della classe sia un intero intero a 64 bit. Nel secondo esempio si ha

int64_t first : 40; 
int32_t second : 24; 

Quali sono due campi di bit separati memorizzati in due blocchi di memoria diversi. Usi 40 bit del numero intero a 64 bit e poi usi 24 bit di un altro intero a 32 bit. Ciò significa che sono necessari almeno 12 byte (questo esempio utilizza 8 byte). Molto probabilmente i 4 byte aggiuntivi che vedete sono padding per allineare la classe sui limiti a 64 bit.

Come altre risposte e commenti hanno sottolineato, questo è un comportamento definito dall'implementazione e puoi/vedrai risultati diversi su diverse implementazioni.

+2

Per quanto posso dire, il * fatto * rilevante * qui è il requisito dello standard C menzionato dalla supercat: "i bitfield devono essere memorizzati * all'interno di * oggetti che sono dei tipi indicati, o il loro equivalente firmato/non firmato. " La tua risposta sembra implicare che 'int32_t' è in qualche modo soggetto a un vincolo * alignment * che è * più * rigoroso di quelli imposti su' int64_t', il che non ha senso. –

+1

@KyleStrand Questo non è quello che sto insinuando. Sto insinuando che il compilatore aggiungesse altri 4 byte alla struct per rendere la dimensione della struct divisibile per 8. In questo modo, se immagazzinato in una matrice, il primo 'int64_t' si estende su due diversi blocchi da 8 byte. – NathanOliver

+0

Questo avrebbe senso come risposta se la domanda originale fosse "perché la dimensione 16 invece di 12, cioè la dimensione totale di' int32_t' più 'int64_t'. " Ma in effetti OP stava chiedendo perché la dimensione aumentasse * del tutto *, cioè, perché il bitfield 'int32_t' non può essere contenuto * all'interno * del tipo' int64_t', poiché c'è spazio sufficiente per archiviare entrambi i bitfield entro 8 bit (dal momento che il le stesse dimensioni dei bitfield non sono cambiate). –

15

Le regole dello standard C per i bitfield non sono sufficientemente precise per dire ai programmatori qualcosa di utile sul layout, ma negano comunque le implementazioni che potrebbero altrimenti essere utili libertà.

In particolare, è necessario memorizzare i bitfield all'interno degli oggetti che corrispondono ai tipi indicati o l'equivalente firmato/non firmato. Nel primo esempio, il primo bitfield deve essere memorizzato in un oggetto int64_t o uint64_t, e il secondo allo stesso modo, ma c'è spazio sufficiente per adattarsi allo stesso oggetto . Nel secondo esempio, il primo bitfield deve essere memorizzato in un int64_t o uint64_t e il secondo in un int32_t o uint32_t. Uint64_t avrà 24 bit che sarebbero "incagliati" anche se ulteriori campi di bit sono stati aggiunti alla fine della struttura; il uint32_t ha 8 bit che non sono attualmente usati, ma sarebbero disponibili per l'uso di un altro bitfield int32_t o uint32_t la cui larghezza era inferiore a 8 sono stati aggiunti al tipo.

IMHO, lo standard colpisce proprio il peggior equilibrio possibile tra dare ai compilatori la libertà e fornire ai programmatori informazioni utili/controllo, ma è quello che è. Personalmente ritengo che i bitfield sarebbero molto più utili se la sintassi preferita consentisse ai programmatori di specificare il loro layout esattamente in termini di oggetti ordinari (ad esempio, il bitfield "foo" dovrebbe essere memorizzato in 3 bit, a partire dal bit 4 (valore di 16), del campo " foo_bar ") ma non conosco piani per definire una cosa del genere nello Standard.

+1

"i bitfield devono essere memorizzati all'interno di oggetti che sono dei tipi indicati, o il loro equivalente firmato/non firmato." Citando lo standard per eseguire il backup di questo reclamo non sarebbe male. – CodesInChaos

+6

C11: "Un'implementazione può allocare qualsiasi unità di memoria indirizzabile abbastanza grande da contenere un campo di bit.Se rimane abbastanza spazio, un campo di bit che segue immediatamente un altro campo di bit in una struttura deve essere impacchettato in bit adiacenti della stessa unità .Se lo spazio insufficiente rimane, se un campo di bit che non si adatta viene inserito nell'unità successiva o si sovrappone alle unità adiacenti è definito dall'implementazione. "Non ho trovato nulla riguardo all'essere costretti a memorizzare all'interno dell'oggetto del tipo dichiarato –

+1

c'è forse una tale limitazione in uno standard precedente, supercat? –

5

standard dice:

§ 9.6 bit-field

Assegnazione dei bit-field all'interno di una oggetto di classe è definito dall'implementazione. L'allineamento dei campi di bit è definito dall'implementazione. [Nota: I campi di bit si trovano su unità di allocazione su alcune macchine e non su su altri.I campi bit vengono assegnati da destra a sinistra su alcune macchine, da sinistra a destra su altri. - nota end]

c++11 paper

Così il layout dipende dalla realizzazione del compilatore, flag di compilazione, arco bersaglio e così via. Appena controllato diversi compilatori e l'uscita per lo più è 8 8:

#include <stdint.h> 
#include <iostream> 

class Test32 
{ 
    int64_t first : 40; 
    int32_t second : 24; 
}; 

class Test64 
{ 
    int64_t first : 40; 
    int64_t second : 24; 
}; 

int main() 
{ 
    std::cout << sizeof(Test32) << " " << sizeof(Test64); 
} 
6

Per aggiungere a ciò che altri hanno già detto:

Se si desidera esaminare, è possibile utilizzare l'opzione compilatore o programma esterno per l'uscita del struct disposizione.

Considerate questo file:

// test.cpp 
#include <cstdint> 

class Test_1 { 
    int64_t first : 40; 
    int64_t second : 24; 
}; 

class Test_2 { 
    int64_t first : 40; 
    int32_t second : 24; 
}; 

// Dummy instances to force Clang to output layout. 
Test_1 t1; 
Test_2 t2; 

Se usiamo una bandiera uscita di layout, come ad esempio Visual Studio /d1reportSingleClassLayoutX (dove X è tutto o parte del nome della classe o struct) o Clang ++ s '-Xclang -fdump-record-layouts (dove -Xclang racconta la compilatore per interpretare -fdump-record-layouts come comando di frontend di Clang invece di un comando di frontend GCC), possiamo scaricare i layout di memoria di Test_1 e Test_2 sullo standard output. [Purtroppo, non sono sicuro di come fare questo direttamente con GCC.]

Se lo facciamo, il compilatore di uscita i seguenti layout:

  • Visual Studio:
cl /c /d1reportSingleClassLayoutTest test.cpp 

// Output: 
tst.cpp 
class Test_1 size(8): 
    +--- 
0. | first (bitstart=0,nbits=40) 
0. | second (bitstart=40,nbits=24) 
    +--- 



class Test_2 size(16): 
    +--- 
0. | first (bitstart=0,nbits=40) 
8. | second (bitstart=0,nbits=24) 
    | <alignment member> (size=4) 
    +--- 
  • Clang:
clang++ -c -std=c++11 -Xclang -fdump-record-layouts test.cpp 

// Output: 
*** Dumping AST Record Layout 
    0 | class Test_1 
    0 | int64_t first 
    5 | int64_t second 
    | [sizeof=8, dsize=8, align=8 
    | nvsize=8, nvalign=8] 

*** Dumping IRgen Record Layout 
Record: CXXRecordDecl 0x344dfa8 <source_file.cpp:3:1, line:6:1> line:3:7 referenced class Test_1 definition 
|-CXXRecordDecl 0x344e0c0 <col:1, col:7> col:7 implicit class Test_1 
|-FieldDecl 0x344e1a0 <line:4:2, col:19> col:10 first 'int64_t':'long' 
| `-IntegerLiteral 0x344e170 <col:19> 'int' 40 
|-FieldDecl 0x344e218 <line:5:2, col:19> col:10 second 'int64_t':'long' 
| `-IntegerLiteral 0x344e1e8 <col:19> 'int' 24 
|-CXXConstructorDecl 0x3490d88 <line:3:7> col:7 implicit used Test_1 'void (void) noexcept' inline 
| `-CompoundStmt 0x34912b0 <col:7> 
|-CXXConstructorDecl 0x3490ee8 <col:7> col:7 implicit constexpr Test_1 'void (const class Test_1 &)' inline noexcept-unevaluated 0x3490ee8 
| `-ParmVarDecl 0x3491030 <col:7> col:7 'const class Test_1 &' 
`-CXXConstructorDecl 0x34910c8 <col:7> col:7 implicit constexpr Test_1 'void (class Test_1 &&)' inline noexcept-unevaluated 0x34910c8 
    `-ParmVarDecl 0x3491210 <col:7> col:7 'class Test_1 &&' 

Layout: <CGRecordLayout 
    LLVMType:%class.Test_1 = type { i64 } 
    NonVirtualBaseLLVMType:%class.Test_1 = type { i64 } 
    IsZeroInitializable:1 
    BitFields:[ 
    <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0> 
    <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0> 
]> 

*** Dumping AST Record Layout 
    0 | class Test_2 
    0 | int64_t first 
    5 | int32_t second 
    | [sizeof=8, dsize=8, align=8 
    | nvsize=8, nvalign=8] 

*** Dumping IRgen Record Layout 
Record: CXXRecordDecl 0x344e260 <source_file.cpp:8:1, line:11:1> line:8:7 referenced class Test_2 definition 
|-CXXRecordDecl 0x344e370 <col:1, col:7> col:7 implicit class Test_2 
|-FieldDecl 0x3490bd0 <line:9:2, col:19> col:10 first 'int64_t':'long' 
| `-IntegerLiteral 0x344e400 <col:19> 'int' 40 
|-FieldDecl 0x3490c70 <line:10:2, col:19> col:10 second 'int32_t':'int' 
| `-IntegerLiteral 0x3490c40 <col:19> 'int' 24 
|-CXXConstructorDecl 0x3491438 <line:8:7> col:7 implicit used Test_2 'void (void) noexcept' inline 
| `-CompoundStmt 0x34918f8 <col:7> 
|-CXXConstructorDecl 0x3491568 <col:7> col:7 implicit constexpr Test_2 'void (const class Test_2 &)' inline noexcept-unevaluated 0x3491568 
| `-ParmVarDecl 0x34916b0 <col:7> col:7 'const class Test_2 &' 
`-CXXConstructorDecl 0x3491748 <col:7> col:7 implicit constexpr Test_2 'void (class Test_2 &&)' inline noexcept-unevaluated 0x3491748 
    `-ParmVarDecl 0x3491890 <col:7> col:7 'class Test_2 &&' 

Layout: <CGRecordLayout 
    LLVMType:%class.Test_2 = type { i64 } 
    NonVirtualBaseLLVMType:%class.Test_2 = type { i64 } 
    IsZeroInitializable:1 
    BitFields:[ 
    <CGBitFieldInfo Offset:0 Size:40 IsSigned:1 StorageSize:64 StorageOffset:0> 
    <CGBitFieldInfo Offset:40 Size:24 IsSigned:1 StorageSize:64 StorageOffset:0> 
]> 

Si noti che la versione di Clang I utilizzata per generare questa uscita (quella utilizzata da Rextester) sembra di default per ottimizzare entrambi i bitfield in una singola variabile, e non sono sicuro di come disabilitare questo comportamento.