Question Pourquoi sizeof n'est pas une structure égale à la somme 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?


547
2017-09-23 04:24


origine


Réponses:


Cela est dû au remplissage ajouté pour satisfaire les contraintes d'alignement. Alignement de la structure des données influe à la fois sur la performance 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 légère.
    • Corrigé dans le matériel, pour une performance-dégradation modeste.
    • Ou corrigé par é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 brisées, ce qui entraîne des erreurs subtiles.

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 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, 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 spéciaux #pragma instructions pour modifier les paramètres d'alignement de la structure.


542
2017-09-23 04:27



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 elles sont entassées   pêle-mêle.

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 ce   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 arrange   c'est comme ça:

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

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

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

146
2017-09-23 04:31



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 compiciel cl.exe avec le / Zp option.

Habituellement, il est plus facile pour le processeur d'accéder aux données qui sont un multiple de 4 (ou 8), selon la plate-forme et aussi sur le compilateur.

C'est donc essentiellement une question d'alignement.

Vous devez avoir de bonnes raisons de le changer.


23
2018-05-31 09:27



Cela peut être dû à l'alignement des octets et au remplissage, de sorte que la structure affiche un nombre pair d'octets (ou de mots) sur votre plate-forme. Par exemple dans C sur 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;
}

Avoir des membres dont les tailles (en octets) sont 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 façon à former un seul mot (4 x 8 octets sur ma plateforme 32 bits).

oneInt=4
twoInts=8
someBits=4

11
2018-06-10 15:07



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 membre-ordre en C ++. Les compilateurs peuvent (et certainement) ajouter des pointeurs 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 quasiment sûr membre-ordre est garanti en C, mais je ne compterais pas là-dessus, en écrivant un programme multiplate-forme ou cross-compilateur.


9
2017-09-23 04:27



La taille d'une structure est supérieure à la somme de ses parties à cause 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 modernes est de 32 bits (4 octets). L'accès à la mémoire lorsque les données sont 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 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'accès à data.a fonctionnera correctement car il commence à une limite - 0x400. L'accès à data.b fonctionnera également correctement, car il est à 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 4ème octet est sur la limite suivante). Ainsi, le système devrait faire un deuxième accès mémoire pour obtenir le 4ème octet,

Maintenant, si au lieu de mettre data.c à l'adresse 0x405, le compilateur rembourre la structure de 3 octets et met data.c à l'adresse 0x408, alors le système aurait seulement besoin 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 échange 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 le swap (vitesse au-dessus de 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 é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 (un 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'empilement 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 quelque chose de 11 à 14 (et pour certains systèmes, autant que 32), en fonction de l'emballage par défaut du compilateur.


6
2018-05-04 15:38



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

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


5
2017-09-23 13:38



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 Appliqué à un opérande de structure ou de type union,   le résultat est le nombre total d'octets dans un tel objet,   y compris le rembourrage interne et arrière.

6.7.2.1 Spécificateurs de structure et d'union:

13 ... Il peut y avoir sans nom   remplissage dans un objet de structure, mais pas au début.

et:

15 Il peut y avoir un rembourrage sans nom à l'extrémité d'une structure ou d'une union.

Le nouveau C99 fonction de membre de groupe flexible (struct S {int is[];};) peut également affecter le rembourrage:

16 Dans un 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 groupe flexible. Dans la plupart des situations,   le membre flexible du tableau est ignoré. En particulier, la taille de la structure est comme si   membre de la matrice flexible ont été omis, sauf qu'il peut avoir plus de rembourrage arrière que   l'omission impliquerait.

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

Les éléments suivants sont non spécifiés: ...

  • La valeur des octets de remplissage lors du stockage de valeurs dans des structures ou des unions (6.2.6.1)

C ++ 11 N3337 standard draft

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf

5.3.3 Taille de:

2 Quand 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 la classe:

Un pointeur vers un objet struct de mise en page standard, correctement converti à l'aide d'un reinterpret_cast, pointe vers son   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 sans nom dans un objet struct standard-layout, mais pas au début,   nécessaire pour parvenir à un alignement approprié. - note de fin]

Je connais seulement assez de C ++ pour comprendre la note :-)


5
2017-07-28 21:25



En plus des autres réponses, une structure peut (mais n'a généralement pas) de fonctions virtuelles, auquel cas la taille de la structure inclura aussi l'espace pour le vtbl.


4
2018-02-24 06:46



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. Cela était dû au fait que certains types d'objets sur l'ordinateur cible peuvent être limités par les limites d'adressage
  • "trous de mémoire" taille inclus dans le résultat de l'opérateur sizeof. Le sizeof n'inclut pas la taille du tableau flexible, qui est 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 une certaine assurance au programmeur de 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 sans nom peuvent être inclus dans la structure aux alignements d'adresses requis des éléments adjacents

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

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

Comment fonctionne l'alignement:

  • Le volume occupé par la structure est calculé comme la taille de l'élément unique aligné d'un réseau de telles structures. La structure devrait fin de sorte que le premier élément de la prochaine structure suivante ne viole pas les exigences de l'alignement

Plus d'informations détaillées sont disponibles ici: "Samuel P.Harbison, Guy L.Steele C A Référence, (5.6.2 - 5.6.7)"


3