Pourquoi sizeof pour une structure n’est-il pas égal à la sum de sizeof de chaque membre?

Pourquoi l’opérateur “sizeof” renvoie-t-il une taille plus grande pour une structure que la taille totale des membres de la structure?

Cela est dû au remplissage ajouté pour satisfaire les contraintes d’alignement. L’alignement de la structure des données a un impact à la fois sur la performance et l’exactitude des programmes:

  • Un access mal aligné peut être une erreur SIGBUS (souvent SIGBUS ).
  • Un access mal aligné peut être une erreur logicielle.
    • Soit corrigé dans le matériel, pour une dégradation modeste des performances.
    • Ou corrigé par une émulation dans le logiciel, pour une dégradation sévère des performances.
    • De plus, l’atomicité et les autres garanties de concurrence peuvent être rompues, ce qui entraîne des erreurs subtiles.

Voici un exemple d’utilisation de parameters typiques pour un processeur x86 (tous utilisés en mode 32 et 64 bits):

 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 sortingant les membres par alignement (le sorting par taille suffit pour cela dans les types de base) (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 mises en page de données différentes et incompatibles. Pour cette raison, lorsque vous utilisez des 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 parameters de ligne de commande et / ou des instructions spéciales #pragma pour modifier les parameters d’alignement de la structure.

Alignement des paquets et 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, ints et long ints) s’ils sont bourré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 mettre 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 vous et moi pouvez au moins voir comment les champs b et c se déroulent? En bref, c’est aussi difficile pour le processeur. Par conséquent, la plupart des compilateurs complèteront la structure (comme avec des champs supplémentaires et invisibles) comme ceci:

 +-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+ 

Si vous voulez que la structure ait une certaine taille avec GCC par exemple, utilisez __atsortingbute__((packed)) .

Sous Windows, vous pouvez définir l’alignement sur un octet lorsque vous utilisez le compier cl.exe avec l’ option / Zp .

En général, il est plus facile pour le processeur d’accéder à des données multiples de 4 (ou 8), en fonction de la plate-forme et du compilateur.

Il s’agit donc essentiellement d’alignement.

Vous devez avoir de bonnes raisons de le changer.

Cela peut être dû à un alignement d’octets et à un remplissage, de sorte que la structure génère un nombre pair d’octets (ou 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) est de 4 octets (32 bits), 8 octets (2x 32 bits) et 1 octet (2 + 6 bits) respectivement. 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 à former un seul mot (4 octets 8 bits sur ma plate-forme 32 bits).

 oneInt=4 twoInts=8 someBits=4 

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 de membre en C ++ . Les compilateurs peuvent (et certainement le font) append des membres de structures de base et de pointeurs de tables virtuelles à 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 en conclure qu’une telle garantie est tout simplement impossible.

Je suis certain que l’ ordre des membres est garanti en C , mais je ne compte pas dessus lorsque j’écris un programme multi-plateforme ou un programme de compilation croisée.

La taille d’une structure est supérieure à la sum 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 travaille. La taille préférée des processeurs les plus modernes si 32 bits (4 octets). L’access à la mémoire lorsque les données se trouvent sur ce type de frontière est plus efficace que les éléments qui chevauchent cette limite de taille.

Par exemple. Considérons 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 limite de 32 bits, nous voyons un problème immédiat (en supposant qu’aucun 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 zéro, donc les données sont alignées sur une limite de 32 bits). L’access à data.a fonctionnera correctement car il commence à une limite – 0x400. L’access à data.b fonctionnera également correctement, car il s’agit de l’adresse 0x404 – une autre limite de 32 bits. Mais une structure non alignée placerait 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 4ème octet se trouve à la limite suivante). Donc, le système devrait faire un deuxième access mémoire pour obtenir le 4ème octet,

Maintenant, si au lieu de mettre data.c à l’adresse 0x405, le compilateur remplissait la structure de 3 octets et mettait 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’access à cet élément de 50%. Le rembourrage change l’efficacité de la mémoire pour une efficacité de traitement. Étant donné que les ordinateurs peuvent disposer de très grandes quantités de mémoire (plusieurs gigaoctets), les compilateurs estiment que le swap (vitesse supérieure à la taille) est raisonnable.

Malheureusement, ce problème devient un problème lorsque vous essayez d’envoyer des structures sur un réseau ou même d’écrire les données binarys dans un fichier binary. 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 un code portable (qui ira à plusieurs compilateurs différents), vous devrez probablement accéder à chaque élément de la structure séparément pour garantir un “emballage” correct.

D’un autre côté, différents compilateurs ont des capacités différentes pour gérer l’emballage des structures de données. Par exemple, dans Visual C / C ++, le compilateur prend en charge la commande #pragma pack. Cela vous permettra d’ajuster l’emballage 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.

Il peut le faire si vous avez implicitement ou explicitement défini l’alignement de la structure. Une structure alignée 4 sera toujours un multiple de 4 octets, même si la taille de ses membres est quelque chose qui ne soit pas un multiple de 4 octets.

De plus, une bibliothèque peut être compilée sous x86 avec 32 bits ints et vous pouvez comparer ses composants sur un processus 64 bits pour obtenir un résultat différent si vous le faites manuellement.

C99 N1256 projet standard

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 un type de structure ou d’union, le résultat est le nombre total d’octets dans un tel object, 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 non nommé dans un object de structure, mais pas au début.

et:

15 Il peut y avoir un remplissage sans nom à la fin d’une structure ou d’une union.

La nouvelle fonctionnalité 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; cela s’appelle un membre de tableau flexible. Dans la plupart des cas, le membre de tableau flexible est ignoré. En particulier, la taille de la structure est comme si le membre de tableau flexible était omis, sauf qu’il pouvait avoir plus de remplissage que l’omission impliquerait.

L’annexe J, Problèmes de portabilité, réitère:

Les éléments suivants ne sont pas spécifiés: …

  • La valeur de padding bytes 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 object de cette classe, y compris tout remplissage requirejs pour placer des objects de ce type dans un tableau.

9.2 Membres du groupe :

Un pointeur vers un object struct standard-layout, converti de manière appropriée à l’aide d’un reinterpret_cast, pointe vers son membre initial (ou si ce membre est un champ de bits, puis vers l’unité dans laquelle il réside) et inversement. [Remarque: Il peut donc y avoir un remplissage non nommé dans un object struct standard-layout, mais pas au début, comme cela est nécessaire pour obtenir un alignement approprié. – note finale]

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:

  • des trous de mémoire peuvent apparaître entre deux composants et après le dernier composant. C’est dû au fait que certains types d’objects sur l’ordinateur cible peuvent être limités par les limites de l’adressage.
  • “taille des trous de mémoire” inclus dans le résultat de l’opérateur sizeof. Le sizeof ne comprend pas seulement la taille du tableau flexible, qui est disponible en C / C ++
  • Certaines implémentations du langage vous permettent de contrôler la disposition en mémoire des structures via les options pragma et compilateur

Le langage C fournit une certaine assurance au programmeur quant à la disposition des éléments dans la structure:

  • les compilateurs nécessaires pour assigner une séquence de composants augmentant les adresses mémoire
  • L’adresse du premier composant coïncide avec l’adresse de début de la structure
  • des champs de bits sans nom peuvent être inclus dans la structure aux alignements d’adresse requirejs des éléments adjacents

Problèmes liés à l’alignement des éléments:

  • Différents ordinateurs alignent les bords des objects de différentes manières
  • Différentes ressortingctions sur la largeur du champ de bits
  • Les ordinateurs diffèrent sur la façon de stocker les octets dans un mot (Intel 80×86 et Motorola 68000)

Comment l’alignement fonctionne:

  • Le volume occupé par la structure est calculé comme la taille de l’élément unique aligné d’un tableau de telles structures. La structure doit se terminer de telle sorte que le premier élément de la structure suivante ne viole pas les exigences d’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 les 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 comstacknt les membres afin 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 été capable de 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 alors deux lignes de cache lorsqu’un access aligné ne peut en expulser qu’une.

Certaines architectures doivent piéger les lectures et écritures mal alignées, ainsi que les premières versions de l’architecture ARM (celle qui a évolué pour devenir tous les processeurs mobiles actuels) … eh bien, elles ne font que renvoyer des données incorrectes pour celles-ci. (Ils ont ignoré les bits de poids faible.)

Enfin, notez que les lignes de cache peuvent être arbitrairement grandes et que le compilateur ne tente pas de deviner celles-ci ni 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 une ligne de cache.

TL; DR: l’ alignement est important.