J'ai défini cette structure:
typedef struct
{
char A:3;
char B:3;
char C:3;
char D:3;
char E:3;
} col;
La sizeof(col)
me donne la sortie de 3, mais ne devrait-elle pas être 2? Si je commente un seul élément, le sizeof
est 2. Je ne comprends pas pourquoi: cinq éléments de 3 bits sont égaux à 15 bits, et cela fait 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 conception de la langue jusqu'à présent, je m'attendais à une taille de 2 octets, pas 3.
Étant donné que vous utilisez char
comme 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 des bits que votre structure utilise est de 15, donc la taille idéale pour contenir autant de données serait un 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 2
pour la deuxième structure. Pour tout ce qui est plus grand qu'un 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 la taille quatre pour int
, huit pour long
, etc.
Parce que vous ne pouvez pas avoir un champ de paquet de bits qui s'étend sur la limite d'alignement minimum (qui est de 1 octet), donc ils seront probablement compressé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 l'idée, car le compilateur pourrait les définir comme il préfère)
Les deux premiers champs de bits tiennent dans un seul char
. Le troisième ne peut pas rentrer dans ce char
et en 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 obtient un troisième char
.
La plupart des compilateurs vous permettent de contrôler le remplissage, par exemple en utilisant #pragma
s . 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.
Même si la norme ANSI C spécifie trop peu sur la façon dont les champs de bits sont compressé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 est tenu de la stocker en tant que structure qui contient un ou plusieurs champs anonymes d'un type de stockage "normal", puis de subdiviser logiquement chacun de ces champs en ses parties de champs de bits constitutives. 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 char
est de 8 bits, le compilateur devrait allouer quatre champs de ce type et affecter deux champs de bits à tous sauf un (qui serait dans un champ char
qui lui est propre). Si toutes les déclarations char
avaient été remplacées par short
, il y aurait alors deux champs de type short
, dont l'un contiendrait cinq champs binaires et l'autre contiendrait le deux autres.
Sur un processeur sans restrictions d'alignement, les données pourraient être présentées plus efficacement en utilisant unsigned short
pour les cinq premiers champs et unsigned char
pour les deux derniers, stockant 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 autoriser que s'il existait un type numérique à trois octets qui pourrait être utilisé comme type de "champ externe".
Personnellement, je considère que les champs de bits 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 supporte une syntaxe comme:
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 est autorisée, permettrait au code d'utiliser des champs binaires 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 dans le 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.