« Programmation C/Types avancés » : différence entre les versions

Contenu supprimé Contenu ajouté
→‎Initialisation : reformulation + précision sur bog possible
→‎Alignement et bourrage (padding) : reformulation et précisions
Ligne 76 :
Il s'agit d'un concept relativement avancé, mais qu'il est bien de connaitre pour agir en connaissance de cause. Lorsqu'on déclare une structure, on pourrait naïvement croire que les champs se suivent les uns à la suite des autres en mémoire. Or, il arrive souvent qu'une partie de la mémoire ne soit pas utilisée.
 
Considérez qu'on travaille sur un environnement 32bits<ref>c'est32 àbits. direSur unces environnementsystèmes, les leéditeurs «de motcompilateurs machinefixent »en faitgénéral 4la octets,taille etdu où untype <tt>char</tt> occupeà un8 octet,bits et celle undu type <tt>int</tt> à 32 bits<ref>D'autres choix sont possibles, par exemple en occupefixant 4<tt>CHAR_BITS</tt> à 16 ou 32, mais cela est rare. Cf. [[Programmation C/Types de base#Caractères|le paragraphe sur les caractères]].</ref>. etSupposez maintenant qu'on déclare la structure suivante:
 
<source lang="c">
Ligne 88 :
On pourrait penser que cette structure occupera 6 octets en mémoire, et pourtant, sur la majeure partie des compilateurs, pour ne pas dire tous, on obtiendrait une taille de 12 octets.
 
En fait, les compilateurs insèrent quasi toujours des octets entre les champs pour pouvoir les ''aligner'' sur des adresses qui correspondent à des mots machines. C'Cela est en fait dû à une limitation de la plupart des processeurs, qui ne peuvent lire des « mots » de plus d'un octet que s'ils sont alignés sur un certain adressage. EnDans fait,le toutescas lesd'un variablesordinateur déclaréesdit suivent« cette32 règlebits :», aussipar bienexemple les(dont variablesle localesprocesseur auxcentral fonctions,utilise lesdes champs''registres'' de structures32 bits), lesil paramètresest courant que la mémoire ne puisse être adressée directement que par « bloc » de fonctions32 bits, etcc'est-à-dire que le processeur ne fournisse des instructions que pour lire des blocs de 32 bits.
 
En se représentant la mémoire comme un tableau continu (et avec les mêmes hypothèses que précédemment), on peut tracer le dessin suivant:
Ce comportement est en fait, non seulement dépendant de l'architecture, mais aussi du compilateur. Ce dernier possède en général des options qui permettent de paramétrer avec quelle finesse se fera l'alignement. Ces options sont bien évidemment spécifiques et pas du tout portables.
 
Dans le cas d'un ordinateur dit « 32 bits », par exemple (dont le processeur central utilise des ''registres'' de 32 bits), il est courant que la mémoire ne puisse être adressée directement que par « bloc » de 32 bits. En se représentant la mémoire comme un tableau continu, on peut tracer le dessin suivant:
 
<pre>
Ligne 101 ⟶ 99 :
</pre>
 
Les cases <code>a</code>, <code>b</code>, <code>c</code>, ... représentent des octets, et les ''blocs'' des sections de 32 bits, qui seules peuvent être adressées directement par la machine. Si on suppose qu'une variable de type <code>ma_structure</code> doive être placée en mémoire à partir du bloc numéro N, alors un compilateur pourra, pour des raisons de performance, placer <code>champ1</code> en <code>a</code>, <code>champ2</code> de <code>e</code> à <code>h</code>, et <code>champ3</code> en <code>i</code>. Cela permettrait en effet d'accéder simplement à <code>champ2</code>: unele lectureprocesseur oufournit unedes écritureinstructions permettant de celire champou se fait simplement en lisant oud'écrire écrivantdirectement le bloc N + 1. Dans ce cas, les octets de <code>b</code> à <code>d</code> ne sont pas utilisés; on dit alors que ce sont des octets ''de bourrage'' (ou ''padding'' en anglais).
Un autre compilateur (ou le même, appelé avec des options différentes) peut aussi placer <code>champ2</code> de <code>b</code> à <code>e</code>, et <code>champ3</code> en <code>f</code>, pour optimiser l'utilisation mémoire. Mais alors il devra générer un code plus complexe lors des accès à <code>champ2</code>, le matériel ne lui permettant pas ded'accéder leen faireune directementseule instruction aux 4 octets <tt>b</tt> à <tt>e</tt>. Si, par exemple, il veut écrire une valeur dans <tt>champ2</tt>, il devra:
* extraire 3 octets de la valeur de champ2;
* concaténer à ces 3 octets la valeur de <tt>champ1<tt>;
* écrire la valeur résultante dans le bloc N;
* extraire le dernier octet de la valeur de champ2;
* l'écrire dans le bloc N + 1.
Un tel traitement est beaucoup plus lent qu'une écriture simple dans le bloc N + 1, c'est pourquoi le choix par défaut des compilateurs est d'insérer des octets de bourrage, en sacrifiant un perte de mémoire légère pour gagner en simplicité et en vitesse.
 
Toutes les variables suivent cette contrainte: aussi bien les variables locales aux fonctions, les champs de structures, les paramètres de fonctions, etc.
 
CeL'existence comportementd'octets estde enbourrage et faitleur nombre sont donc, non seulement dépendantdépendants de l'architecture, mais aussi du compilateur. Ce dernier possède en général des options qui permettent de paramétrer avec quelle finesse se fera l'alignement. Ces options sont bien évidemment spécifiques et pas du tout portables.
 
Même si lesces options commandant l'utilisation de bourrage sont spécifiques à chaque compilateur, il peut s'avérer nécessaire de connaître la « distance » (''offset'') d'un champ par rapport au début de la structure, et ce, de manière portable. Pour cela il existe une fonction (en fait, macro), déclarée dans l'entête <code>stddef.h</code>:
 
<source lang="c">