28 votes

Pourquoi la taille de la classe augmente lorsque int64_t passe à int32_t

Dans mon premier exemple, j'ai deux champs binaires utilisant int64_t . Lorsque je compile et que j'obtiens la taille de la classe, j'obtiens 8.

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

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

Mais lorsque je change le deuxième bitfeild en int32_t la taille de la classe double à 16:

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

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

Cela se produit à la fois sur GCC 5.3.0 et MSVC 2015. Mais pourquoi?

37voto

NathanOliver Points 10062

Dans votre premier exemple

int64_t first : 40;
int64_t second : 24;

Les deux first et second utiliser la version 64 bits d'un unique entier de 64 bits. Cela provoque la taille de la classe à un seul entier de 64 bits. Dans le deuxième exemple, vous avez

int64_t first : 40;
int32_t second : 24;

Qui est deux champs de bits stockés dans deux différents morceaux de la mémoire. Vous utilisez de 40 bits de l'entier de 64 bits et que vous utilisez les 24 bits de l'autre un entier de 32 bits. Cela signifie que vous avez besoin d'au moins 12 octets(cet exemple utilise 8 bits octets). Le plus probable de la supplémentaire de 4 octets que vous voyez est le rembourrage pour aligner la classe sur 64 bits limites.

Comme d'autres réponses et commentaires l'ont souligné cette mise en œuvre est définie comportement, et vous pouvez/pourrez voir des résultats différents sur les différentes implémentations.

15voto

supercat Points 25534

Le C de la Norme des règles pour bitfields ne sont pas assez précis pour dire programmeurs quelque chose d'utile sur la mise en page, mais néanmoins refuser implémentations de ce qui pourrait autrement être utile libertés.

En particulier, bitfields doivent être stockées dans des objets qui sont de la des types, ou l'signé/non signé équivalent. Dans ton premier exemple, le premier champ de bits doit être stocké dans une int64_t ou un uint64_t objet, et la deuxième, de la même manière, mais il y a assez de place pour eux de s'insérer dans le même objet. Dans le deuxième exemple, le premier champ de bits doivent être stockés dans un int64_t ou un uint64_t, et la deuxième, dans une int32_t ou uint32_t. L'un uint64_t disposera de 24 bits qui serait "brin", même si d'autres champs de bits ont été ajoutés à la fin de la structure; la uint32_t a 8 bits qui ne sont pas actuellement utilisés, mais ne sera disponible pour l'utilisation d'un autre int32_t ou uint32_t champ de bits dont la largeur est inférieure à 8 ont été ajoutés à la type.

À mon humble avis, la Norme de grèves à peu près le pire possible juste équilibre entre donner les compilateurs de la liberté de vs donner aux développeurs utile de l'information/contrôle, mais elle est ce qu'elle est. Personnellement, je pense bitfields serait beaucoup plus utile si la syntaxe préférée laisser les programmeurs de spécifier leur disposition, précisément en termes d'objets ordinaires (ex: champ de bits "foo" doivent être stockés dans 3 bits, départ à 4 bits (valeur de 16 ans), de champ "foo_bar") mais je ne connais pas les plans de définir une telle chose dans la Norme.

6voto

Justin Time Points 563

Pour ajouter à ce que d'autres ont déjà dit:

Si vous voulez examiner, vous pouvez utiliser une option du compilateur ou un programme externe à la sortie de la structure de mise en page.

Considérer ce fichier:

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

Si l'on utilise une mise en page indicateur de sortie, tels que Visual Studio /d1reportSingleClassLayoutX (où X tout ou partie de la catégorie ou de la struct nom) ou Clang++s' -Xclang -fdump-record-layouts (où -Xclang indique au compilateur d'interpréter -fdump-record-layouts comme un Clang interface commande au lieu de GCC interface de commande), nous pouvons faire un dump de la mémoire dispositions de l' Test_1 et Test_2 sur la sortie standard. [Malheureusement, je ne suis pas sûr de la façon de le faire directement avec GCC.]

Si nous le faisons, le compilateur génère les dispositions suivantes:

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

Notez que la version de Clang j'ai utilisé pour générer cette sortie (celui utilisé par Rextester) s'affiche par défaut en optimisant à la fois bitfields en une seule variable, et je ne suis pas sûr comment faire pour désactiver ce comportement.

5voto

Lorond Points 448

La norme dit:

§ 9.6 bits-champs

L'Allocation de bit-domaines au sein d'une classe l'objet est la mise en œuvre définies. L'alignement de la bit-champs définis par l'implémentation. [Note: Bit-champs de chevauchement des unités d'allocation sur certaines machines et pas sur d'autres. Peu de champs sont affectés de droite à gauche sur certaines machines, de gauche à droite sur les autres. - la note de fin]

c++11 papier

Donc, mise en page dépend du compilateur mise en œuvre, des indicateurs de compilation, cible arc et ainsi de suite. Juste vérifié plusieurs compilateurs et de sortie est la plupart du temps 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);
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X