Pourquoi sizeof pour un struct n'est-il pas égal à la somme de sizeof de chaque membre?

StackOverflow https://stackoverflow.com/questions/119123

Question

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

Était-ce utile?

La solution

Cela est dû au remplissage ajouté pour satisfaire les contraintes d’alignement. L'alignement de la structure de données a une incidence sur les performances et l'exactitude des programmes:

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

Voici un exemple d'utilisation des paramètres types d'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 réduire la taille des structures en triant les membres par alignement (le tri 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 dispositions 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 paramètres de ligne de commande et / ou des instructions spéciales #pragma pour modifier les paramètres d'alignement de la structure.

Autres conseils

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 à 2 et 4 octets   quantités (par exemple, ints et long ints) si ils sont entassés dans   à tous les niveaux.

     

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 d’emballer cette   structure en mémoire comme ceci:

+-------+-------+-------+-------+
|           a           |   b   |
+-------+-------+-------+-------+
|   b   |           c           |
+-------+-------+-------+-------+
|   c   |           d           |
+-------+-------+-------+-------+
     

Mais c'est beaucoup, beaucoup plus facile pour le processeur si le compilateur arrange   ça comme ça:

+-------+-------+-------+
|           a           |
+-------+-------+-------+
|       b       |
+-------+-------+-------+-------+
|               c               |
+-------+-------+-------+-------+
|           d           |
+-------+-------+-------+
     

Dans la version emballée, remarquez comme c'est au moins un peu difficile pour   vous et moi pour voir comment les champs de b et c s'enroulent? En un mot,   c'est aussi difficile pour le processeur. Par conséquent, la plupart des compilateurs compileront   la structure (comme si avec des champs supplémentaires et invisibles) comme ceci:

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

Si vous souhaitez que la structure ait une certaine taille avec GCC, par exemple, utilisez __ attribut __ ((emballé)) .

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

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

Il s’agit donc d’une question d’alignement.

Vous devez avoir de bonnes raisons de le changer.

Cela peut être dû à l'alignement et au remplissage des octets, de sorte que la structure comporte 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 les tailles (en octets) sont respectivement 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 - la dernière structure étant complétée de sorte qu’il s’agisse d’un seul mot (4 x 8 octets sur ma plate-forme 32 bits).

oneInt=4
twoInts=8
someBits=4

Voir aussi:

pour Microsoft Visual C:

http://msdn.microsoft .com / fr-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

Outre les 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 le font certainement) 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 garantie par la norme (la mise en oeuvre du mécanisme virtuel n’est pas précisée) et on peut donc en conclure que cette garantie est tout simplement impossible.

Je suis tout à fait sûr que l'ordre des membres est garanti en C , mais je ne compterais pas dessus pour l'écriture d'un programme multi-plateforme ou multi-compilateur. .

La taille d'une structure est supérieure à la somme de ses parties en raison de ce que l'on appelle l'emballage. Un processeur particulier a une taille de données préférée avec laquelle il fonctionne. La taille préférée des processeurs modernes est 32 bits (4 octets). L'accès à la mémoire lorsque les données se trouvent sur ce type de limite est plus efficace que les éléments chevauchant 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 limite de 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 zéro, les données sont donc alignées sur une limite de 32 bits). L'accès à data.a fonctionnera correctement car il commence sur une limite - 0x400. L'accès à data.b fonctionnera également correctement 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 un ordinateur 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 sur la limite suivante). Donc, le système devrait faire un deuxième accès en mémoire pour obtenir le 4ème octet,

Maintenant, si au lieu de mettre data.c à l'adresse 0x405, le compilateur complétait la structure de 3 octets et plaçait data.c à l'adresse 0x408, le système n'aurait alors besoin que d'un cycle pour lire les données, réduisant ainsi 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 disposer d’énormes quantités de mémoire (plusieurs giga-octets), les compilateurs estiment que l’échange (vitesse supérieure à la taille) est raisonnable.

Malheureusement, ce problème devient mortel 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. Pour é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 le compactage 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 aller de 11 à 14 (et pour certains systèmes, jusqu'à 32), en fonction de la compression par défaut du compilateur.

Il peut le faire si vous avez défini de manière implicite ou explicite 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 ne représente pas un multiple de 4 octets.

De plus, une bibliothèque peut être compilée sous x86 avec des ints 32 bits et vous pouvez comparer ses composants sur un processus 64 bits vous donnerait un résultat différent si vous le faisiez à la main.

Projet de norme 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 ayant 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 non-nommé   remplissage dans un objet de structure, mais pas à son début.

et:

  

15 Il peut y avoir un remplissage non nommé à la fin d'une structure ou d'une union.

La nouvelle fonctionnalité de membre de tableau flexible C99 ( struct S {int est [ ];}; ) peut également affecter le remplissage:

  

16 Comme cas particulier, le dernier élément d’une structure comportant plusieurs membres nommés peut   avoir un type de tableau incomplet; c'est ce qu'on appelle un membre de groupe flexible. Dans la plupart des situations,   le membre de tableau flexible est ignoré. En particulier, la taille de la structure est comme si le   membre de la matrice flexible ont été omis, sauf qu'il peut avoir plus de remplissage de fin que   l'omission impliquerait.

L'Annexe J, Problèmes de portabilité réitère:

  

Ne sont pas spécifiés: ...

     
      
  • Valeur des octets de remplissage lors de l'enregistrement de valeurs dans des structures ou des unions (6.2.6.1)
  •   

Projet de norme 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 de classe :

  

Un pointeur sur un objet struct standard-layout, converti de manière appropriée à l'aide d'un reinterpret_cast, pointe sur son objet.   membre initial (ou si ce membre est un champ de bits, puis à l'unité dans laquelle il réside) et vice versa. [ Remarque:   Il peut donc y avoir un remplissage non nommé dans un objet struct standard-layout, mais pas au début,   si nécessaire pour obtenir un alignement approprié. & # 8212; 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:

  • des trous de mémoire peuvent apparaître entre deux composants quelconques et après le dernier composant. Cela était dû au fait que certains types d’objets sur l’ordinateur cible pouvaient être limités par les limites de l’adressage
  • " trous de mémoire " taille incluse dans le résultat de l'opérateur sizeof. Le sizeof n'inclut que la taille du tableau flexible, disponible en C / C ++
  • Certaines implémentations du langage vous permettent de contrôler la disposition de la mémoire des structures à l'aide des options pragma et compilateur

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

  • compilateurs requis pour affecter une séquence de composants augmentant les adresses de mémoire
  • L'adresse du premier composant coïncide avec l'adresse de début de la structure
  • des champs de bits non nommés peuvent être inclus dans la structure aux alignements d'adresse requis des éléments adjacents

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

  • Différents ordinateurs bordent les bords des objets de différentes manières
  • Différentes restrictions sur la largeur du champ de bits
  • Les ordinateurs diffèrent quant à la manière de stocker les octets dans un mot (Intel 80x86 et Motorola 68000)

Fonctionnement de l'alignement:

  • 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 devrait se termine de sorte que le premier élément de la structure suivante ne viole pas les exigences d'alignement

p.s Des informations plus détaillées sont disponibles ici: "Samuel P. Harbison, Guy L. Référence Cte ??Lte A, (5.6.2 - 5.6.7)"

L’idée est que, pour des raisons de rapidité et de cache, les opérandes doivent être lus à partir d’adresses alignées sur leur taille naturelle. Pour ce faire, les compilateurs compilent les membres de la structure afin que le membre suivant ou le suivant 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éfaut d’alignement recouvre deux lignes de cache différentes, il en supprime deux alors qu’un accès aligné en exclurait une seule.

Certaines architectures doivent en effet intercepter des lectures et des écritures mal alignées, ainsi que les premières versions de l’architecture ARM (celle qui a évolué pour devenir l’ensemble des processeurs mobiles actuels). (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 ni de faire un compromis entre espace et vitesse. Au lieu de cela, les décisions d’alignement font partie de l’ABI et représentent l’alignement minimum permettant de remplir uniformément une ligne de cache.

TL; DR: l'alignement est important.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top