Pourquoi l' sizeof
opérateur renvoie-t-il une taille plus grande pour une structure que la taille totale des membres de la structure?
Pourquoi l' sizeof
opérateur renvoie-t-il une taille plus grande pour une structure que la taille totale des membres de la structure?
Réponses:
Cela est dû au remplissage ajouté pour satisfaire les contraintes d'alignement. L'alignement de la structure des données a un impact sur les performances et l'exactitude des programmes:
SIGBUS
).Voici un exemple utilisant des paramètres typiques pour un processeur x86 (tous les modes 32 et 64 bits utilisés):
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
On peut minimiser la taille des structures en triant les membres par alignement (le tri par taille suffit pour cela dans les types basiques) (comme la structure Z
dans l'exemple ci-dessus).
REMARQUE IMPORTANTE: les normes C et C ++ indiquent que l'alignement de la structure est défini par l'implémentation. Par conséquent, chaque compilateur peut choisir d'aligner les données différemment, ce qui entraîne des dispositions de données différentes et incompatibles. Pour cette raison, lorsqu'il s'agit de bibliothèques qui seront utilisées par différents compilateurs, il est important de comprendre comment les compilateurs alignent les données. Certains compilateurs ont des paramètres de ligne de commande et / ou des #pragma
instructions spéciales pour modifier les paramètres d'alignement de la structure.
Emballage et alignement des octets, comme décrit dans la FAQ C ici :
C'est pour l'alignement. De nombreux processeurs ne peuvent pas accéder à des quantités de 2 et 4 octets (par exemple, les entiers et les entiers longs) s'ils sont entassés dans tous les sens.
Supposons que vous ayez cette structure:
struct { char a[3]; short int b; long int c; char d[3]; };
Maintenant, vous pourriez penser qu'il devrait être possible de compresser cette structure en mémoire comme ceci:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
Mais c'est beaucoup, beaucoup plus facile sur le processeur si le compilateur l'arrange comme ceci:
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
Dans la version compactée, remarquez comment il est au moins un peu difficile pour vous et moi de voir comment les champs b et c s'enroulent? En un mot, c'est difficile pour le processeur aussi. Par conséquent, la plupart des compilateurs remplissent la structure (comme avec des champs supplémentaires et invisibles) comme ceci:
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
alors &s.a == &s
et &s.d == &s + 12
(étant donné l'alignement indiqué dans la réponse). Le pointeur n'est stocké que si les tableaux ont une taille variable (par exemple, a a
été déclaré à la char a[]
place de char a[3]
), mais les éléments doivent alors être stockés ailleurs.
Si vous voulez que la structure ait une certaine taille avec GCC par exemple, utilisez __attribute__((packed))
.
Sous Windows, vous pouvez définir l'alignement sur un octet lorsque vous utilisez le compilateur cl.exe avec l' option / Zp .
Habituellement, il est plus facile pour le CPU d'accéder à des données qui sont un multiple de 4 (ou 8), selon la plate-forme et également sur le compilateur.
C'est donc essentiellement une question d'alignement.
Vous devez avoir de bonnes raisons de le changer.
Cela peut être dû à l'alignement des octets et au remplissage afin que la structure émette un nombre pair d'octets (ou de mots) sur votre plate-forme. Par exemple en C sous Linux, les 3 structures suivantes:
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};
int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
Les membres dont la taille (en octets) sont respectivement de 4 octets (32 bits), 8 octets (2x 32 bits) et 1 octet (2 + 6 bits). Le programme ci-dessus (sous Linux utilisant gcc) imprime les tailles 4, 8 et 4 - où la dernière structure est complétée de manière à ce qu'il s'agisse d'un seul mot (4 x 8 octets sur ma plate-forme 32 bits).
oneInt=4
twoInts=8
someBits=4
:2
et :6
spécifient en fait des entiers 2 et 6 bits, et non 32 bits dans ce cas. someBits.x, étant seulement 2 bits, ne peut stocker que 4 valeurs possibles: 00, 01, 10 et 11 (1, 2, 3 et 4). Est-ce que ça a du sens? Voici un article sur la fonctionnalité: geeksforgeeks.org/bit-fields-c
Voir également:
pour Microsoft Visual C:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
et GCC revendiquent la compatibilité avec le compilateur de Microsoft .:
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
En plus des réponses précédentes, veuillez noter que quel que soit l'emballage, il n'y a pas de garantie de commande des membres en C ++ . Les compilateurs peuvent (et certainement le font) ajouter un pointeur de table virtuelle et des membres de structures de base à la structure. Même l'existence d'une table virtuelle n'est pas assurée par la norme (l'implémentation du mécanisme virtuel n'est pas spécifiée) et on peut donc conclure qu'une telle garantie est tout simplement impossible.
Je suis sûr que l' ordre des membres est garanti en C , mais je ne compterais pas dessus lors de l'écriture d'un programme multiplateforme ou compilateur croisé.
La taille d'une structure est supérieure à la somme de ses parties en raison de ce qu'on appelle l'emballage. Un processeur particulier a une taille de données préférée avec laquelle il fonctionne. Taille préférée des processeurs les plus modernes si 32 bits (4 octets). Accéder à la mémoire lorsque des données se trouvent sur ce type de limite est plus efficace que les éléments qui chevauchent cette limite de taille.
Par exemple. Considérez la structure simple:
struct myStruct
{
int a;
char b;
int c;
} data;
Si la machine est une machine 32 bits et que les données sont alignées sur une frontière 32 bits, nous voyons un problème immédiat (en supposant qu'il n'y ait pas d'alignement de structure). Dans cet exemple, supposons que les données de structure commencent à l'adresse 1024 (0x400 - notez que les 2 bits les plus bas sont nuls, donc les données sont alignées sur une limite de 32 bits). L'accès à data.a fonctionnera bien car il commence sur une limite - 0x400. L'accès à data.b fonctionnera également très bien, car il se trouve à l'adresse 0x404 - une autre limite de 32 bits. Mais une structure non alignée mettrait data.c à l'adresse 0x405. Les 4 octets de data.c sont à 0x405, 0x406, 0x407, 0x408. Sur une machine 32 bits, le système lirait data.c pendant un cycle de mémoire, mais n'obtiendrait que 3 des 4 octets (le 4e octet se trouve sur la limite suivante). Donc, le système devrait faire un deuxième accès à la mémoire pour obtenir le 4e octet,
Maintenant, si au lieu de mettre data.c à l'adresse 0x405, le compilateur a complété la structure de 3 octets et mis data.c à l'adresse 0x408, alors le système n'aurait besoin que d'un cycle pour lire les données, réduisant le temps d'accès à cet élément de données de 50%. Le rembourrage permute l'efficacité de la mémoire pour l'efficacité du traitement. Étant donné que les ordinateurs peuvent avoir d'énormes quantités de mémoire (plusieurs gigaoctets), les compilateurs estiment que l'échange (vitesse par rapport à la taille) est raisonnable.
Malheureusement, ce problème devient un tueur lorsque vous essayez d'envoyer des structures sur un réseau ou même d'écrire les données binaires dans un fichier binaire. Le remplissage inséré entre les éléments d'une structure ou d'une classe peut perturber les données envoyées au fichier ou au réseau. Afin d'écrire du code portable (celui qui ira à plusieurs compilateurs différents), vous devrez probablement accéder à chaque élément de la structure séparément pour assurer le bon "emballage".
D'un autre côté, différents compilateurs ont des capacités différentes pour gérer l'empaquetage de la structure de données. Par exemple, dans Visual C / C ++, le compilateur prend en charge la commande #pragma pack. Cela vous permettra d'ajuster la compression et l'alignement des données.
Par exemple:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
Je devrais maintenant avoir la longueur de 11. Sans le pragma, je pourrais être n'importe quoi de 11 à 14 (et pour certains systèmes, jusqu'à 32), en fonction de l'emballage par défaut du compilateur.
#pragma pack
. Si les membres sont alloués sur leur alignement par défaut, je dirais généralement que la structure n'est pas compactée.
Il peut le faire si vous avez implicitement ou explicitement défini l'alignement de la structure. Une structure alignée sur 4 sera toujours un multiple de 4 octets même si la taille de ses membres serait quelque chose qui n'est pas un multiple de 4 octets.
Une bibliothèque peut également être compilée sous x86 avec des entiers 32 bits et vous pourriez comparer ses composants sur un processus 64 bits vous donnerait un résultat différent si vous le faisiez à la main.
Projet standard C99 N1256
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 L'opérateur sizeof :
3 Lorsqu'il est appliqué à un opérande qui a une structure ou un type d'union, le résultat est le nombre total d'octets dans un tel objet, y compris le remplissage interne et final.
6.7.2.1 Spécificateurs de structure et d'union :
13 ... Il peut y avoir un remplissage sans nom dans un objet de structure, mais pas à son début.
et:
15 Il peut y avoir un rembourrage sans nom à la fin d'une structure ou d'union.
La nouvelle fonction de membre de tableau flexible C99 ( struct S {int is[];};
) peut également affecter le remplissage:
16 Comme cas particulier, le dernier élément d'une structure avec plus d'un membre nommé peut avoir un type de tableau incomplet; c'est ce qu'on appelle un membre de tableau flexible. Dans la plupart des situations, le membre du tableau flexible est ignoré. En particulier, la taille de la structure est comme si le membre de tableau flexible était omis, sauf qu'il peut avoir plus de remplissage de fin que l'omission impliquerait.
L'annexe J, Questions de portabilité, réitère:
Les éléments suivants ne sont pas spécifiés: ...
- La valeur des octets de remplissage lors du stockage de valeurs dans des structures ou des unions (6.2.6.1)
Projet standard C ++ 11 N3337
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3 Taille de :
2 Lorsqu'il est appliqué à une classe, le résultat est le nombre d'octets dans un objet de cette classe, y compris tout remplissage requis pour placer des objets de ce type dans un tableau.
9.2 Membres du groupe :
Un pointeur vers un objet struct de mise en page standard, convenablement converti à l'aide d'un reinterpret_cast, pointe vers son membre initial (ou si ce membre est un champ binaire, puis vers l'unité dans laquelle il réside) et vice versa. [Remarque: Il peut donc y avoir un remplissage sans nom dans un objet struct de mise en page standard, mais pas au début, comme nécessaire pour obtenir un alignement approprié. - note de fin]
Je ne connais que suffisamment de C ++ pour comprendre la note :-)
En plus des autres réponses, une structure peut (mais généralement pas) avoir des fonctions virtuelles, auquel cas la taille de la structure inclura également l'espace pour le vtbl.
Le langage C laisse au compilateur une certaine liberté quant à l'emplacement des éléments structurels dans la mémoire:
Le langage C fournit une certaine assurance au programmeur de la disposition des éléments dans la structure:
Problèmes liés à l'alignement des éléments:
Fonctionnement de l'alignement:
ps Des informations plus détaillées sont disponibles ici: "Samuel P.Harbison, Guy L.Steele CA Reference, (5.6.2 - 5.6.7)"
L'idée est que pour des considérations de vitesse et de cache, les opérandes doivent être lus à partir d'adresses alignées à leur taille naturelle. Pour ce faire, les compilateurs compilent les membres de sorte que le membre suivant ou la structure suivante soit aligné.
struct pixel {
unsigned char red; // 0
unsigned char green; // 1
unsigned int alpha; // 4 (gotta skip to an aligned offset)
unsigned char blue; // 8 (then skip 9 10 11)
};
// next offset: 12
L'architecture x86 a toujours pu récupérer des adresses mal alignées. Cependant, il est plus lent et lorsque le désalignement chevauche deux lignes de cache différentes, il expulse deux lignes de cache lorsqu'un accès aligné n'en expulse qu'une.
Certaines architectures doivent en fait piéger les lectures et les écritures mal alignées, et les premières versions de l'architecture ARM (celle qui a évolué dans tous les CPU mobiles d'aujourd'hui) ... eh bien, elles ont en fait simplement renvoyé de mauvaises données. (Ils ont ignoré les bits de poids faible.)
Enfin, notez que les lignes de cache peuvent être arbitrairement grandes et que le compilateur n'essaie pas de les deviner ou de faire un compromis espace / vitesse. Au lieu de cela, les décisions d'alignement font partie de l'ABI et représentent l'alignement minimum qui remplira éventuellement uniformément une ligne de cache.
TL; DR: l' alignement est important.