Pourquoi la taille de la classe augmente lorsque int64 t passe à int32 t

Dans mon premier exemple, j'ai deux champs de bits en utilisant int64_t. Quand je compile et 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 quand je change le second bitfeild pour être un 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?

28
demandé sur curiousguy 2016-07-12 20:54:07

4 réponses

Dans votre premier exemple

int64_t first : 40;
int64_t second : 24;

Les Deux first et second utilisent les 64 bits d'un seul 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 séparés stockés dans deux morceaux de mémoire différents. Vous utilisez 40 bits de l'entier 64 bits, puis vous utilisez 24 bits d'un autre entier 32 bits. Cela signifie que vous avez besoin d'au moins 12 octets(cet exemple utilise 8 bits octets). Très probablement le supplément 4 octets que vous voyez est un remplissage pour aligner la classe sur les limites de 64 bits.

Comme d'autres réponses et commentaires l'ont souligné, il s'agit d'un comportement défini par l'implémentation et vous pouvez/verrez différents résultats sur différentes implémentations.

36
répondu NathanOliver 2016-07-12 19:10:57

Les règles de la norme C pour les champs de bits ne sont pas assez précises pour indiquer aux programmeurs quelque chose d'utile sur la mise en page, mais refusent néanmoins aux implémentations ce qui pourrait autrement être des libertés utiles.

En particulier, les champs de bits doivent être stockés dans les objets des types indiqués, ou leur équivalent signé/non signé. Dans votre premier exemple, le premier bitfield doit être stocké dans un objet int64_t ou uint64_t, et le second de même, mais il y a assez de place pour qu'ils s'intègrent dans le même objet. Dans le second exemple, le premier bitfield doit être stocké dans un int64_t ou uint64_t, et le second dans un int32_t ou uint32_t. l'uint64_t aura 24 bits qui seraient "bloqués" même si des champs de bits supplémentaires étaient ajoutés à la fin de la structure; l'uint32_t a 8 bits qui ne sont pas actuellement utilisés, mais qui seraient disponibles pour l'utilisation d'un autre champ de bits int32_t ou uint32_t dont la largeur était inférieure à 8 ont été ajoutés au type.

Mon humble avis, la norme frappe à peu près le pire équilibre possible ici entre donner aux compilateurs la liberté et donner aux programmeurs des informations/contrôles utiles, mais c'est ce que c'est. Personnellement, je pense que bitfields serait beaucoup plus utile si la syntaxe préférée permettait aux programmeurs de spécifier leur disposition précisément en termes d'objets ordinaires (par exemple bitfield "foo" devrait être stocké en 3 bits, en commençant par le bit 4 (valeur de 16), du champ "foo_bar") mais je ne connais aucun plan pour définir une telle chose dans la norme.

15
répondu supercat 2016-07-12 18:12:40

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

Si vous voulez l'examiner, Vous pouvez utiliser une option de compilateur ou un programme externe pour afficher la structure.

Considérez 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 nous utilisons un indicateur de sortie de layout, tel que /d1reportSingleClassLayoutX de Visual Studio (où X est tout ou partie de la classe ou du nom de la structure) ou -Xclang -fdump-record-layouts de Clang++(où -Xclang indique au compilateur d'interpréter -fdump-record-layouts comme une commande frontend Clang au lieu d'une commande frontend GCC), nous pouvons vider la mémoire mises en page de Test_1 et Test_2 à la sortie standard. [Malheureusement, je ne suis pas sûr de savoir comment le faire directement avec GCC.]

Si nous le faisons, le compilateur affichera les mises en page suivantes:

  • Studio Visuel:
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 que j'ai utilisée pour générer cette sortie (celle utilisée par Rextester) semble par défaut optimiser les deux champs de bits en une seule variable, et je ne sais pas comment le désactiver comportement.

6
répondu Justin Time 2016-07-12 21:26:55

Standard dit:

§ 9.6 champs de bits

Allocation de champs de bits dans une classe l'objet est la mise en œuvre définies. L'alignement des champs de bits est défini par l'implémentation. [Remarque: les champs de bits chevauchent les unités d'allocation sur certaines machines et non sur d'autres. Les champs de bits sont assignés de droite à gauche sur certaines machines, de gauche à droite sur d'autres. - note de fin ]

C++11 papier

La mise en page dépend donc de l'implémentation du compilateur, drapeaux de compilation, arc cible et ainsi de suite. Juste vérifié plusieurs compilateurs et la sortie est principalement 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);
}
5
répondu lorond 2016-07-13 08:02:42