Pourquoi cette structure est-elle de taille 3 au lieu de 2?

91

J'ai défini cette structure:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

Le sizeof(col)donnez-moi la sortie de 3, mais ne devrait-il pas être 2? Si je commente un seul élément, le sizeofest 2. Je ne comprends pas pourquoi: cinq éléments de 3 bits sont égaux à 15 bits, et c'est moins de 2 octets.

Y a-t-il une «taille interne» dans la définition d'une structure comme celle-ci? J'ai juste besoin d'une clarification, car d'après ma notion du langage jusqu'à présent, je m'attendais à une taille de 2 octets, pas de 3.

Raffaello
la source
4
C'est probablement une optimisation de l'alignement. Il commence un nouvel octet, si la taille de bit suivante ne rentre pas dans l'espace occupé réel.
πάντα ῥεῖ
4
À moins que certaines contraintes externes n'exigent le conditionnement de bits et que votre plate-forme offre des garanties supplémentaires par rapport à ce que propose le standard, il est inutile d'utiliser des champs de bits.
David Rodríguez - dribeas
3
Notez que pour C, l'utilisation de char est moins portable que l'utilisation de int, stackoverflow.com/a/23987436/23118 .
hlovdal
2
Notez que presque tout ce qui concerne les champs de bits est défini par l'implémentation. Vous pourriez obtenir des réponses différentes de différents compilateurs, et il n'y aurait aucun recours. Notez également que parce que vous n'avez pas spécifié signed charou unsigned char, vous ne pouvez pas dire sans regarder la documentation si le compilateur traitera 'plain' chardans un champ de bits comme signé ou non signé, et la décision pourrait (en théorie) être différente de la décision de savoir si 'plain' charest signé ou non signé lorsqu'il est utilisé en dehors d'un champ de bits.
Jonathan Leffler
3
Plus précisément, dans C99, §6.7.2.1 Struct et spécificateurs syndicales, ¶4 A-champ bit doit avoir un type qui est une version qualifiée ou non de _Bool, signed int, unsigned intou un autre type défini par l' implémentation. L'utilisation chartombe donc dans la catégorie «autre type défini par l'implémentation».
Jonathan Leffler

Réponses:

95

Étant donné que vous utilisez charcomme type sous-jacent pour vos champs, le compilateur essaie de regrouper les bits par octets, et comme il ne peut pas mettre plus de huit bits dans chaque octet, il ne peut stocker que deux champs par octet.

La somme totale de bits utilisée par votre structure est de 15, donc la taille idéale pour contenir autant de données serait a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Le code ci-dessus (pour une plate-forme 64 bits comme la mienne) donnera en effet 2pour la deuxième structure. Pour tout ce qui est plus grand que a short, la structure ne remplira pas plus d'un élément du type utilisé, donc - pour cette même plate-forme - la structure se retrouvera avec une taille quatre pour int, huit pour long, etc.

didierc
la source
1
La définition de structure proposée est toujours erronée. La définition de structure correcte utiliserait «unsigned short».
user3629249
21
@ user3629249 Pourquoi le court-circuit non signé est-il «correct»? Si l'utilisateur veut stocker de -4 à 3, alors short est correct. Si l'utilisateur veut stocker de 0 à 7, le short non signé est correct. La question d'origine utilisait un type signé, mais je ne peux pas dire si c'était intentionnel ou accidentel.
Bruce Dawson
2
Pourquoi y a-t-il une différence entre charet short?
GingerPlusPlus
5
@BruceDawson: La norme permet aux implémentations d' charêtre non signées…
Thomas Eding
@ThomasEding True, la norme permet à char d'être non signé. Mais mon point principal demeure, qu'aucune raison n'a été donnée pour prétendre que le short non signé était correct (bien qu'il le soit généralement ).
Bruce Dawson
78

Parce que vous ne pouvez pas avoir un champ de bits qui s'étend sur la limite d'alignement minimum (qui est de 1 octet), ils seront probablement emballés comme

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(les ordres de champ / remplissage à l'intérieur du même octet ne sont pas intentionnels, c'est juste pour vous donner une idée, puisque le compilateur pourrait les définir comme il préfère)

Jack
la source
16

Les deux premiers champs de bits correspondent à un seul char. Le troisième ne peut pas entrer dans cela charet a besoin d'un nouveau. 3 + 3 + 3 = 9 qui ne rentre pas dans un caractère 8 bits.

Ainsi, la première paire prend un char, la deuxième paire prend un char, et le dernier champ de bits en obtient un troisième char.

2501
la source
15

La plupart des compilateurs vous permettent de contrôler le remplissage, par exemple en utilisant #pragmas . Voici un exemple avec GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Notez que le comportement par défaut du compilateur est là pour une raison et vous donnera probablement de meilleures performances.

Kos
la source
9

Même si la norme ANSI C spécifie trop peu sur la façon dont les champs de bits sont emballés pour offrir un avantage significatif par rapport aux "compilateurs sont autorisés à emballer les champs de bits comme bon leur semble", elle interdit néanmoins dans de nombreux cas aux compilateurs de conditionner les choses de la manière la plus efficace.

En particulier, si une structure contient des champs de bits, un compilateur doit la stocker sous la forme d'une structure qui contient un ou plusieurs champs anonymes d'un certain type de stockage «normal», puis subdiviser logiquement chacun de ces champs en ses parties de champ de bits constituantes. Ainsi, étant donné:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Si unsigned charest 8 bits, le compilateur devrait allouer quatre champs de ce type, et attribuer deux champs de bits à tous sauf un (ce qui serait dans un charchamp qui lui est propre). Si toutes les chardéclarations avaient été remplacées par short, alors il y aurait deux champs de type short, dont l'un contiendrait cinq champs de bits et l'autre contiendrait les deux autres.

Sur un processeur sans restrictions d'alignement, les données pourraient être présentées plus efficacement en utilisant unsigned shortpour les cinq premiers champs et unsigned charpour les deux derniers, le stockage de sept champs de trois bits sur trois octets. Alors qu'il devrait être possible de stocker huit champs de trois bits sur trois octets, un compilateur ne pourrait permettre cela que s'il existait un type numérique de trois octets qui pourrait être utilisé comme type "champ externe".

Personnellement, je considère que les champs de bits tels que définis sont fondamentalement inutiles. Si le code doit fonctionner avec des données binaires, il doit définir explicitement les emplacements de stockage des types réels, puis utiliser des macros ou d'autres moyens similaires pour accéder à leurs bits. Il serait utile que C prenne en charge une syntaxe telle que:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Une telle syntaxe, si elle était autorisée, permettrait au code d'utiliser des champs de bits de manière portable, sans tenir compte de la taille des mots ou de l'ordre des octets (foo0 serait dans les trois bits les moins significatifs de f1, mais ceux-ci pourraient être stockés au adresse inférieure ou supérieure). En l'absence d'une telle fonctionnalité, cependant, les macros sont probablement le seul moyen portable de fonctionner avec de telles choses.

supercat
la source
2
Différents compilateurs disposeront les champs de bits différemment. J'ai écrit une documentation sur la façon dont Visual C ++ le fait qui peut être pertinente. Il souligne certains des pièges gênants: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Eh bien, vous dites un équivalent de stocker dans un type normal et utilisez l'opérateur de champ de bits pour accomplir la variable unique d'intérêt et pour simplifier ce mécanisme, utilisez une macro. Je pense que le code généré en c / c ++ fait quelque chose comme ça aussi. Utiliser une structure est juste pour une "meilleure" organisation du code, en fait pas du tout nécessaire.
Raffaello