2016-07-12 26 views
28

In meinem ersten Beispiel habe ich zwei Bitfelder mit int64_t. Wenn ich die Größe der Klasse kompilieren und erhalten erhalte ich 8.Warum die Klassengröße zunimmt, wenn int64_t in int32_t geändert wird

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

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

Aber wenn ich die zweite bitfeild ändern ein int32_t die Größe der Klasse zu sein, verdoppelt sich auf 16:

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

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

Dies geschieht auf sowohl GCC 5.3.0 und MSVC 2015. Aber warum?

+11

** Größe ** erhöht, nicht Ausrichtung. Im ersten Fall sind der erste und der zweite Teil desselben int64_t. Im zweiten Fall können sie offensichtlich nicht. –

+0

Versuchen Sie, Adressen von Feldern zu erhalten, oder noch besser - post-generierte Assemblierung von Code, der auf beide Felder zugreift. Oder zumindest - welchen Compiller verwendest du? – lorond

+0

@lorond GCC 5.3.0 – xinaiz

Antwort

35

Im ersten Beispiel

int64_t first : 40; 
int64_t second : 24; 

Sowohl firstsecond und die 64 Bits einer einzigen 64-Bit-Ganzzahl verwendet werden. Dies bewirkt, dass die Größe der Klasse eine einzelne 64-Bit-Ganzzahl ist. Im zweiten Beispiel haben Sie

int64_t first : 40; 
int32_t second : 24; 

Das ist zwei separate Bitfelder, die in zwei verschiedenen Teilen des Speichers gespeichert werden. Sie verwenden 40 Bit der 64-Bit-Ganzzahl und dann 24 Bit einer anderen 32-Bit-Ganzzahl. Dies bedeutet, dass Sie mindestens 12 Byte benötigen (dieses Beispiel verwendet 8-Bit-Bytes). Höchstwahrscheinlich sind die zusätzlichen 4 Bytes, die Sie sehen, Padding, um die Klasse an 64-Bit-Grenzen auszurichten.

Wie andere Antworten und Kommentare darauf hingewiesen haben, handelt es sich um ein implementierungsdefiniertes Verhalten, und Sie können/werden bei verschiedenen Implementierungen unterschiedliche Ergebnisse sehen.

+2

Soweit ich das beurteilen kann, ist die * einzige * relevante Tatsache hier die Anforderung von C-Standard, die von supercat erwähnt wird: "Bitfelder müssen * in * Objekten der angegebenen Typen gespeichert werden, oder das vorzeichenbehaftete/vorzeichenlose Äquivalent davon. " Ihre Antwort scheint zu implizieren, dass 'int32_t' irgendwie einer * alignment * -Einschränkung unterliegt, die * strenger * als die für 'int64_t' auferlegten ist, was keinen Sinn ergibt. –

+1

@KyleStrand Das ist nicht, was ich unterstelle. Ich unterstelle, dass der Compiler der Struktur weitere 4 Byte hinzugefügt hat, um die Strukturgröße durch 8 teilbar zu machen. Auf diese Weise überspannt die erste "int64_t", wenn sie in einem Array gespeichert ist, zwei verschiedene 8-Byte-Blöcke. – NathanOliver

+0

Das würde als Antwort Sinn machen, wenn die ursprüngliche Frage "warum ist die Größe 16 statt 12, d. H. Die Gesamtgröße von" int32_t "plus" int64_t "." Aber tatsächlich fragte OP, warum die Größe überhaupt * zunimmt, dh warum das 'int32_t'-Bitfeld nicht innerhalb * des 'int64_t'-Typs enthalten sein kann, da genügend Platz ist, um beide Bitfelder innerhalb von 8 Bits zu speichern (seit dem Bitfeldgrößen selbst haben sich nicht geändert). –

15

Die Regeln des C-Standards für Bitfelder sind nicht präzise genug, um Programmierern etwas Nützliches über das Layout zu sagen, aber Implementierungen, die ansonsten nützliche Freiheiten wären, zu verweigern.

Insbesondere müssen die Bitfelder innerhalb Objekte der angegebenen Typen oder das vorzeichenbehaftete/vorzeichenlose Äquivalent davon gespeichert werden. In Ihrem ersten Beispiel muss das erste Bitfeld in einem int64_t- oder uint64_t-Objekt, , und das zweite Bit ebenfalls gespeichert werden, aber es gibt genug Platz, damit sie in das gleiche Objekt passen. Im zweiten Beispiel muss das erste Bitfeld in einer int64_t oder uint64_t und die zweite in einem int32_t oder uint32_t gespeichert werden. Uint64_t hat 24 Bits, die "gestrandet" wären, selbst wenn zusätzliche Bitfelder am Ende der Struktur hinzugefügt würden; Das Uint32_t hat 8 Bits, die derzeit nicht verwendet werden, aber für die Verwendung eines anderen Bitfeldes int32_t oder uint32_t verfügbar wären, dessen Breite weniger als 8 zum Typ hinzugefügt wurde.

IMHO, schlägt der Standard gerade über die schlechteste mögliche Balance hier zwischen dem Geben von Compilern Freiheit vs. dem Geben von Programmierern nützlicher Information/Kontrolle, aber es ist, was es ist. Persönlich denke ich, Bitfelder wären viel nützlicher, wenn die bevorzugte Syntax es den Programmierern erlauben würde, ihr Layout präzise in Bezug auf gewöhnliche Objekte zu spezifizieren (zB Bitfeld "foo" sollte in 3 Bits gespeichert werden, beginnend mit Bit 4 (Wert von 16), von Feld " foo_bar "), aber ich kenne keine Pläne, so etwas im Standard zu definieren.

+1

"Bitfelder müssen in Objekten gespeichert werden, die von den angegebenen Typen sind, oder das vorzeichenbehaftete/vorzeichenlose Äquivalent davon." Die Angabe des Standards zur Sicherung dieses Anspruchs würde nicht schaden. – CodesInChaos

+6

C11: "Eine Implementierung kann jede adressierbare Speichereinheit zuweisen, die groß genug ist, um ein Bitfeld zu halten. Wenn genug Platz bleibt, wird ein Bitfeld, das unmittelbar einem anderen Bitfeld in einer Struktur folgt, in benachbarte Bits derselben Einheit gepackt .Wenn nicht genügend Speicherplatz vorhanden ist, wird die Implementierung definiert, ob ein Bitfeld, das nicht in die nächste Einheit passt oder benachbarte Einheiten überlappt, implementiert wurde. "Ich habe nichts über das Speichern in einem Objekt des deklarierten Typs gefunden. –

+1

gibt es vielleicht eine solche Einschränkung in einem früheren Standard, supercat? –

5

Norm sagt:

§ 9.6 Bitfelder

Zuordnung der Bit-Felder innerhalb einer Klasse Objekt ist die Implementierung definiert. Die Ausrichtung von Bitfeldern ist implementierungsdefiniert. [Hinweis: Bit-Felder Straddle Zuordnungseinheiten auf einigen Computern und nicht auf anderen.Bit-Felder werden bei einigen Computern von rechts nach links zugewiesen, bei anderen von links nach rechts. - Endnote]

c++11 paper

So hängt das Layout auf Compiler Implementierung, Erstellung Flaggen, Zielbogen und so weiter. So überprüft mehrere Compiler und Ausgang meist ist 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

Um hinzuzufügen, was andere schon gesagt:

Wenn Sie es untersuchen wollen, können Sie eine Compiler-Option oder ein externes Programm zur Ausgabe der Struktur verwenden Layout.

Betrachten Sie diese Datei:

// 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; 

Wenn wir verwenden, um ein Layout Ausgabe-Flag, wie Visual Studio /d1reportSingleClassLayoutX (wo X alle oder einen Teil der Klasse oder Struktur Name ist) oder Clang ++ 's -Xclang -fdump-record-layouts (wo -Xclang sagt die Compiler zu interpretieren -fdump-record-layouts als ein Clang-Frontend-Befehl anstelle eines GCC-Frontend-Befehls), können wir die Speicherlayouts von Test_1 und Test_2 auf Standard-Ausgang ausgeben. [Leider, ich bin nicht sicher, wie dies mit GCC direkt zu tun.]

Wenn wir das tun, so dass die Compiler erzeugen folgende Ausgabe der folgenden Layouts:

  • 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> 
]> 

Beachten Sie, dass die Version von Clang, die zum Generieren dieser Ausgabe verwendet wurde (die von Rextester verwendete), standardmäßig beide Bitfelder in einer einzigen Variablen zu optimieren, und ich bin mir nicht sicher, wie Sie dieses Verhalten deaktivieren.