web-dev-qa-db-fra.com

Structure de la mémoire en C

J'ai un fond C #. Je suis vraiment un débutant dans une langue de bas niveau comme C.

En C #, la mémoire de struct est définie par défaut par le compilateur. Le compilateur peut réorganiser les champs de données ou ajouter des bits supplémentaires entre les champs de manière implicite. J'ai donc dû spécifier un attribut spécial pour redéfinir ce comportement pour une présentation exacte.

Autant que je sache, C ne réorganise ni n’aligne la structure de mémoire d’un struct par défaut. Cependant, j'ai entendu dire qu'il y avait une petite exception très difficile à trouver.

Quel est le comportement de la mémoire de C? Qu'est-ce qui devrait être réordonné/aligné et non?

75
Eonil

En C, le compilateur est autorisé à dicter un alignement pour chaque type de primitive. En règle générale, l'alignement correspond à la taille du type. Mais cela dépend entièrement de la mise en œuvre.

Des octets de remplissage sont introduits pour que chaque objet soit correctement aligné. La réorganisation n'est pas autorisée.

Peut-être chaque compilateur moderne à distance implémente-t-il #pragma pack qui permet de contrôler le remplissage et laisse le programmeur se conformer à l’ABI. (C'est strictement non standard, cependant.)

À partir de C99 § 6.7.2.1:

12 Chaque membre de champ non binaire d'un objet de structure ou d'union est aligné d'une manière définie par la mise en œuvre et adaptée à son type.

13 Dans un objet de structure, les membres autres que les champs de bits et les unités dans lesquelles résident les champs de bits ont des adresses qui augmentent dans l'ordre dans lequel ils ont été déclarés. Un pointeur sur un objet de structure, converti de manière appropriée, 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. Il peut y avoir un remplissage non nommé dans un objet de structure, mais pas à son début.

101
Potatoswatter

C'est spécifique à l'implémentation, mais en pratique la règle (en l'absence de #pragma pack Ou similaire) est la suivante:

  • Les membres de la structure sont stockés dans l'ordre dans lequel ils ont été déclarés. (Ceci est requis par la norme C99, comme mentionné précédemment.)
  • Si nécessaire, un remplissage est ajouté avant chaque membre de structure pour assurer un alignement correct.
  • Chaque type de primitive T nécessite un alignement de sizeof(T) octets.

Donc, étant donné la structure suivante:

struct ST
{
   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
};
  • ch1 Est à l'offset 0
  • un octet de remplissage est inséré pour aligner ...
  • s au décalage 2
  • ch2 Est à l'offset 4, immédiatement après s
  • 3 octets de remplissage sont insérés pour aligner ...
  • ll à l'offset 8
  • i est à l'offset 16, juste après ll
  • 4 octets de remplissage sont ajoutés à la fin, de sorte que la structure globale est un multiple de 8 octets. J'ai vérifié cela sur un système 64 bits: les systèmes 32 bits peuvent permettre aux structures d'avoir un alignement de 4 octets.

Donc, sizeof(ST) vaut 24.

Il peut être réduit à 16 octets en réorganisant les membres pour éviter le remplissage:

struct ST
{
   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
} ST;
103
dan04

Vous pouvez commencer par lire le article de wikipedia sur l'alignement de la structure des données pour mieux comprendre l'alignement des données.

De la article de wikipedia :

L'alignement des données consiste à placer les données à un décalage de mémoire égal à un multiple de la taille de Word, ce qui augmente les performances du système en raison de la façon dont le processeur gère la mémoire. Pour aligner les données, il peut être nécessaire d'insérer des octets sans signification entre la fin de la dernière structure de données et le début du suivant, qui correspond au remplissage de la structure de données.

D'après 6.54.8 Structure-Emballage Pragmas de la documentation GCC:

Pour assurer la compatibilité avec les compilateurs Microsoft Windows, GCC prend en charge un ensemble de directives #pragma qui modifient l'alignement maximal des membres des structures (autres que les champs de bits de largeur nulle), des unions et des classes définies ultérieurement. La valeur n ci-dessous doit toujours être une petite puissance de deux et spécifie le nouvel alignement en octets.

  1. #pragma pack(n) définit simplement le nouvel alignement.
  2. #pragma pack() définit l'alignement sur celui qui était en vigueur au début de la compilation (voir aussi l'option de ligne de commande -fpack-struct [=] voir Options de génération de code).
  3. #pragma pack(Push[,n]) applique le réglage d'alignement actuel sur une pile interne, puis définit éventuellement le nouvel alignement.
  4. #pragma pack(pop) restaure le paramètre d'alignement sur celui enregistré en haut de la pile interne (et supprime cette entrée de pile). Notez que #pragma pack([n]) n'influence pas cette pile interne; ainsi, il est possible d'avoir #pragma pack(Push) suivi de plusieurs instances de #pragma pack(n) et finalisé par un seul #pragma pack(pop).

Certaines cibles, par exemple i386 et powerpc, prennent en charge la ms_struct #pragma, qui établit une structure sous la forme documentée __attribute__ ((ms_struct)).

  1. #pragma ms_struct on Active la mise en page des structures déclarées.
  2. #pragma ms_struct off Désactive la mise en page pour les structures déclarées.
  3. #pragma ms_struct reset Revient à la disposition par défaut.
9
jschmier