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

Contenu supprimé Contenu ajouté
→‎Alignement et bourrage (padding) : reformulation et précisions
Tpierron (discussion | contributions)
Ligne 74 :
 
=== Alignement et bourrage (padding) ===
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 partiedes deoctets lasoient mémoireintercalés neentre soitles pas utiliséechamps.
 
Considérez la structure suivante:
Considérez qu'on travaille sur un environnement 32 bits. Sur ces systèmes, les éditeurs de compilateurs fixent en général la taille du type <tt>char</tt> à 8 bits et celle du type <tt>int</tt> à 32 bits<ref>D'autres choix sont possibles, par exemple en fixant <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>. Supposez maintenant qu'on déclare la structure suivante:
 
<source lang="c">
Ligne 86 :
</source>
 
On pourrait penser que cette structure occupera 6 octets en mémoire, et pourtant, sur laune majeurebonne partie des compilateurs, pour ne pas dire tous, on obtiendrait une taille deplus proche des 12 octets.
 
En fait, les compilateurs insèrent quasiquasiment toujours des octets entre les champs pour pouvoir les ''aligner'' sur des adresses qui correspondent à des mots machines. Cela est 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. Dans le cas d'un ordinateur dit « 32 bits », par exemple (dont le processeur central utilise desalors qu''registres'' de 32 bits), il estn'y couranta quepas lade mémoirecontrainte neparticulière puissepour êtrelire adresséeun directementseul que par « bloc » de 32 bitsoctet, c'est-à-dire quesi le processeurtype ne<tt>char</tt> fournisseest desdéfini instructionssur que pour lire des blocs de 32 bits8bit).
 
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:
 
<pre>
Ligne 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>: le processeur fournit des instructions permettant de lire ou d'écrire directement 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 d'accéder en une seule 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.
 
ToutesEn fait il faut garder à l'esprit que toutes les variables suivent cette contrainte: aussi bien les variables locales aux fonctions, les champs de structures, les paramètres de fonctions, etc.
 
L'existence d'octets de bourrage etainsi que leur nombre sont donc, non seulement dépendants de l'architecture, mais aussi du compilateur. CeCela dernierdit, possèdeil enest généraltoujours despossible optionsde quiconnaître permettentla « distance » (''offset'') d'un champ par rapport au début de paramétrerla avecstructure, quelleet finessece, sede feramanière portable. Pour cela il existe une macro, déclarée dans l'alignemententête <code>stddef.h</code>:
 
Même si ces options 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 macro), déclarée dans l'entête <code>stddef.h</code>:
 
<source lang="c">
Ligne 152 ⟶ 144 :
<pre>L'offset de 'champ2' vaut 4.</pre>
 
En fait toute cette section était pour souligner le fait qu'il est difficilement portable de comparer les structures comme des blocs binaires (via la fonction <code>memcmp</code> par exemple), car il n'y a aucune garantie que ces octets de bourrage soient intialisés à une certaine valeur. SiDe vousla voulezmême comparermanière, uneil est sage de prendre quelques précautions avant de transférer cette structure à l'extérieur du programme (comme un fichier, un tube de communication ou une socket IP). En général, il estpréférable nécessairede d'ytraiter procéderla structure champ par champ, pour ce genre d'opérations.
 
<references />