Programmation C/Types avancés
Structures.
struct ma_structure {
 type1 champ1;
 type2 champ2;
 typeN champN;
} var1, var2, ..., varM;
Déclare une structure (ou enregistrement) "ma_structure" composé de N champs, "champ1" de type "type1", "champ2" de type "type2", etc. On déclare, par la même occasion, M variables de type "struct ma_structure".
Accès aux champs.
L'accès aux champs d'une structure se fait avec un point :
struct complexe {
 int reel;
 int imaginaire;
} c;
c.reel = 1;
c.imaginaire = 2;
Initialisation.
Il y a plusieurs façons d'initialiser une variable de type structure :
Manipulation.
La seule opération prise en charge par le langage est la copie, lors des affectations ou des passages de paramètres à des fonctions. Toutes les autres opérations sont à la charge du programmeur, notamment la comparaison d'égalité (cf. section suivante).
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 que des octets soient intercalés entre les champs.
Considérez la structure suivante:
struct ma_structure {
 char champ1; /* 8 bits */
 int champ2; /* 32 bits */
 char champ3; /* 8 bits */
On pourrait penser que cette structure occupera 6 octets en mémoire, et pourtant, sur une bonne partie des compilateurs, on obtiendrait une taille plus proche des 12 octets.
En fait, les compilateurs insèrent quasiment 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 (alors qu'il n'y a pas de contrainte particulière pour lire un seul octet, si le type codice_1 est défini sur 8bit).
En se représentant la mémoire comme un tableau continu, on peut tracer le dessin suivant:
Les cases codice_2, codice_3, codice_4, ... représentent des octets, et les "blocs" des sections de 32 bits. Si on suppose qu'une variable de type codice_5 doive être placée en mémoire à partir du bloc numéro N, alors un compilateur pourra, pour des raisons de performance, placer codice_6 en codice_2, codice_8 de codice_9 à codice_10, et codice_11 en codice_12. Cela permettrait en effet d'accéder simplement à codice_8: le processeur fournit des instructions permettant de lire ou d'écrire directement le bloc N + 1. Dans ce cas, les octets de codice_3 à codice_15 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 codice_8 de codice_3 à codice_9, et codice_11 en codice_20, pour optimiser l'utilisation mémoire. Mais alors il devra générer un code plus complexe lors des accès à codice_8, le matériel ne lui permettant pas d'accéder en une seule instruction aux 4 octets codice_3 à codice_9.
En 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 ainsi que leur nombre sont non seulement dépendants de l'architecture, mais aussi du compilateur. Cela dit, il est toujours possible 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 codice_24:
size_t offsetof(type, champ);
La valeur renvoyée est le nombre de codice_1 (i.e. d'octets la plupart du temps), entre le début de la structure et celui du champ. Le premier argument attendu est bel et bien le "type" de la structure et non une variable de ce type, ni un pointeur vers une variable de ce type. Pour s'en souvenir, il suffit de savoir que beaucoup d'implémentations d'codice_26 utilisent une arithmétique de ce genre:
size_t distance = (size_t) &((type *)NULL)->champ;
Si codice_27 était un pointeur, il faudrait faire un déréférencement supplémentaire (ou éviter l'étoile dans la macro). À noter que, même si cette macro peut s'avérer contraignante (notamment lorsqu'on ne dispose que de type pointeur), il est quand même préférable de l'utiliser pour des raisons de portabilité.
Voici un petit exemple d'utilisation de cette macro:
struct ma_structure {
 char champ1;
 int champ2;
 char champ3;
int main(void)
 /* en C99 */
 printf("L'offset de 'champ2' vaut %zu.\n",
 offsetof(struct ma_structure, champ2));