Programmation C source/types avancés
/* inclut stdio.h qui décrit
 - la fonction printf */
/* inclut stddef.h qui décrit
 - la fonction offsetof */
/* inclut string.h qui décrit
 - la fonction memcmp */
/* inclut stdlib.h qui décrit
 - la fonction malloc */
int main(void)
 printf("--------------------\n");
 printf(" Structures\n");
 printf("--------------------\n");
 struct ma_structure {
 char champ1;
 int champ2;
 double champN;
 } var1, var2, varM;
 /* crée 3 variables var1, var2 et varM
 de type struct ma_structure */
 printf("\n--------------------\n");
 printf(" Accès aux champs\n");
 printf("--------------------\n");
 var1.champ1 = 'x';
 var1.champ2 = 3;
 var1.champN = 4.6;
 /* accès aux champs de la variable var1
 de type ma_structure */
 printf("\n--------------------\n");
 printf(" Initilisation\n");
 printf(" lors de la\n");
 printf(" déclaration\n");
 printf("--------------------\n");
 struct ma_structure var3 = { 'a', 12345, 0.123 };
 /* déclaration de var3 de type ma_structure
 et initialisation var3.champ1 = a,
 var3.champ2 = 12345, etc.
 si on oublie des champs ils seront mis à 0
 attention l'initialisation doit se faire dans l'ordre */
 struct ma_structure var4 = { .champ2 = 6789, .champ1 = 'b', .champN = 12.3 };
 /* valide en C version C99
 on initialise ainsi dans le désordre les champs
 si on oublie des champs ils seront mis à 0 */
 printf("\n--------------------\n");
 printf(" Opération de copie\n");
 printf("--------------------\n");
 struct ma_structure var5 = {};
 /* déclaration de var5 de type struct ma_structure
 et initialisation à zero */
 printf("var5.champ1 = %c\n", var5.champ1);
 printf("var5.champ2 = %d\n", var5.champ2);
 printf("var5.champN = %f\n", var5.champN);
 var5 = var4;
 // copie de var4 dans var5
 printf("\nvar5.champ1 = %c\n", var5.champ1);
 printf("var5.champ2 = %d\n", var5.champ2);
 printf("var5.champN = %f\n", var5.champN);
 printf("\n--------------------\n");
 printf(" Une structure\n");
 printf(" en mémoire\n");
 printf("--------------------\n");
 struct ma_structure1 {
 char champ1; // 8 bits
 int champ2; // 32 bits
 char champ3; // 8 bits
 struct ma_structure1 ma_variable1;
 /* Comment est placée une structure en mémoire ?
 mémoire :
 | bloc N | bloc N + 1 | bloc N + 2 |
 | a | b | c | d | e | f | g | h | i | j | k | l | 
 les cases a, b, c, ... représentent des octets
 les blocs des sections de 32 bits
 on suppose qu'une variable ma_variable1 de type struct ma_structure1
 doive être placée en mémoire à partir du bloc numéro N
 alors un compilateur pourra, pour des raisons de performance,
 placer champ1 en a, champ2 de e à h, et champ3 en i
 Cela permettrait en effet d'accéder simplement à champ1, champ2, champ3:
 le processeur fournit des instructions permettant de lire
 ou d'écrire directement le bloc N, N + 1, etc.
 Dans ce cas, les octets de b à d 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 champ2 de b à e, et champ3 en f, pour optimiser l'utilisation mémoire.
 Mais alors il devra générer un code plus complexe lors des accès à champ2,
 le matériel ne lui permettant pas d'accéder en une seule instruction aux 4 octets b à e.
 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. */
 printf("L'offset de champ2 vaut %zu.\n", offsetof(struct ma_structure1, champ2));
 /* offsetof connaître la distance (en anglais offset) d'un champ
 par rapport au début de la structure
 la valeur renvoyée est le nombre de char
 ceci est valable pour C norme C99 */
 printf("L'offset de champ2 vaut %lu.\n", (unsigned long) offsetof(struct ma_structure1, champ2));
 // en C90
 // La plupart du temps offsetof est écrit comme ceci :
 size_t distance = (size_t) &((struct ma_structure1 *)NULL)->champ2;
 /* explication :
 NULL est l'adresse nulle
 or un pointeur est/contient une adresse
 (struct ma_structure1 *)NULL transforme NULL en pointeur de type struct ma_structure1
 on demande champ2 à ce pointeur ((struct ma_structure1 *)NULL)->champ2
 et plus précisément l'adresse de champ2 &((struct ma_structure1 *)NULL)->champ2
 qu'on transforme en size_t (size_t) &((struct ma_structure1 *)NULL)->champ2;
 size_t est un type de variable capable de stocker la taille en char
 d'un objet (une variable, un tableau, etc.)
 dans notre cas c'est pas vraiment une taille mais une distance qu'on calcule */
 printf("adresse de champ2 = %p\n", &((struct ma_structure1 *)NULL)->champ2);
 printf("distance = %zu\n", distance);
 struct ma_structure1 var6 = { 'a', 1, 'a' }, var7 = { 'a', 1, 'a' };
 printf("comparaison = %d\n", memcmp(&var6, &var7, sizeof(struct ma_structure1)));
 /* on pourrait par exemple comparer la mémoire occupée
 par 2 structures différentes
 c'est ce que fait la fonction memcmp au niveau des bits
 elle renvoie
 0 si var6 = var7
 -1 si var6 < var7
 +1 si var6 > var7
 pourtant à cause des octets de bourrage
 le résultat est déconcertant
 donc à ne pas faire ! */
 printf("comparaison champ1 = %d\n", memcmp(&var6.champ1, &var7.champ1, sizeof (char)));
 printf("comparaison champ2 = %d\n", memcmp(&var6.champ2, &var7.champ2, sizeof (int)));
 printf("comparaison champ3 = %d\n", memcmp(&var6.champ3, &var7.champ3, sizeof (char)));
 // par contre on peut comparer chaque champ
 /* De la même manière, il 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 est préférable de traiter la structure champ par champ,
 pour ce genre d'opérations. */
 printf("\n--------------------\n");
 printf(" Pointeurs\n");
 printf(" vers structures\n");
 printf("--------------------\n");
 struct ma_structure1 * variable_pointeur1;
 // déclaration d'un pointeur de type struct ma_structure1
 variable_pointeur1 = malloc(sizeof(struct ma_structure1));
 /* allocation dynamique, on réserve de la mémoire
 pour une structure, malloc renvoit l'adresse de début
 de la mémoire réservée */
 (* variable_pointeur1).champ1 = 'z';
 /* accéder à champ1: on déréférence le pointeur avec *
 (on précise qu'on veut accéder à la valeur et pas l'adresse)
 * s'applique ici au pointeur donc on met des parenthèses
 et on rajoute le champ par .champ1 */
 variable_pointeur1->champ1 = 'y';
 /* idem exactement pareil, en C on peut utiliser ->
 pour accéder à un champ dans le cas d'un pointeur */
 free(variable_pointeur1);
 // libère la mémoire alloué dynamiquement
 printf("\n--------------------\n");
 printf(" Unions\n");
 printf("--------------------\n");
 union mon_union1 {
 char champ1; // 8 bits
 int champ2; // 32 bits
 // déclaration d'un type d'union
 union mon_union1 varA;
 // déclaration d'une variable de type union mon_union1
 /* une union est en fait une variable qui peut
 etre dans notre cas soit un champ1 soit un champ2
 notre union va faire 32 bits
 et pas 8 + 32 bits
 elle prend la taille du plus grand champ (champ2 > champ1)
 si on utilise notre union comme champ1
 on lit/écrit sur les 8 premiers bits de champ1 et champ2
 sinon sur 32 bits */
 varA.champ1 = 'B';
 printf("varA.champ1 = %c\n", varA.champ1);
 printf("varA.champ2 = %d\n", varA.champ2);
 /* résultat
 varA.champ1 = B
 varA.champ2 = -1208516798
 il faut savoir que la mémoire n'est pas remise à zéro
 donc quand on a déclaré varA il y a un nombre aléatoire dedans
 donc B qui vaut 66 en ascii sur 8bits
 accédé via champ2 se voit ajouter 24 autres bits
 qui ne sont pas à zéro ce qui donne un nombre aléatoire */
 varA.champ2 = 65;
 printf("\nvarA.champ1 = %c\n", varA.champ1);
 printf("varA.champ2 = %d\n", varA.champ2);
 /* résultat
 varA.champ1 = A
 varA.champ2 = 65
 par chance 65 n'est pas trop grand et vaut A en ascii
 on comprend qu'une union est une seule variable */
 varA.champ1 = 'B';
 printf("\nvarA.champ1 = %c\n", varA.champ1);
 printf("varA.champ2 = %d\n", varA.champ2);
 /* résultat
 varA.champ1 = B
 varA.champ2 = 66
 par chance B vaut 66 en ascii et les autres bits sont à zéro
 ce qui fait qu'on a 66 on accédant à varA.champ2 */
 varA.champ2 = 10082;
 printf("\nvarA.champ1 = %c\n", varA.champ1);
 printf("varA.champ2 = %d\n", varA.champ2);
 /* résultat
 varA.champ1 = b
 varA.champ2 = 10082
 (10082 en binaire voir les derniers 8 bits -> 98 -> b) */
 varA.champ1 = 'R';
 printf("\nvarA.champ1 = %c\n", varA.champ1);
 printf("varA.champ2 = %d\n", varA.champ2);
 /* résultat
 varA.champ1 = R
 varA.champ2 = 10066
 (les 8 derniers bits sont modifiés, en ascii R -> 66) */
 /* Dans un cas réel on utilise soit varA.champ1 soit varA.champ2
 mais pas les 2, à cause des valeurs abérrantes */
 printf("\n--------------------\n");
 printf(" typedef\n");
 printf("--------------------\n");
 typedef char caractere;
 // typedef définit un synonyme du type
 char varX = 'a';
 caractere varY = 'a';
 /* c'est la même chose
 char ou caractere */
 // quelques autres exemples
 typedef unsigned char octet;
 typedef double matrice4_4[4][4];
 typedef struct ma_structure * ma_struct;
 typedef void (*gestionnaire_t)( int );
 // Utilisation
 octet nombre = 255;
 matrice4_4 identite = { {1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0,0,1} };
 ma_struct pointeur = NULL;
 gestionnaire_t pointeur_sur_fonction = NULL;
 /* autre exemple:
 déclaration de ma_fonction confuse */
 void (* ma_fonction(int varC, void (*pointeur_sur_fonction)(int)) )(int)
 // ...
 return pointeur_sur_fonction;
 // typedef
 typedef void (*psf)(int);
 // déclaration claire grace au typedef
 psf fonction(int varC, psf f)
 // ...
 return f;
 printf("\n--------------------\n");
 printf(" Enumérations\n");
 printf("--------------------\n");
 enum jours_de_la_semaine { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche };
 // déclaration d'un type enum jours_de_la_semaine
 enum jours_de_la_semaine courant = lundi;
 // déclaration d'une variable courant de type enum jours_de_la_semaine
 printf("courant = %d\n", courant);
 courant = mardi;
 printf("courant = %d\n", courant);
 courant = mercredi;
 printf("courant = %d\n", courant);
 enum Booleen1 {Vrai1 = 1, Faux1};
 // Vrai1 = 1 première valeur, donc Faux1 deuxième valeur = 2
 enum Booleen1 variable1 = Faux1;
 printf("\nvariable1 = %d\n", variable1);
 enum Booleen2 {Vrai2 = 1, Faux2 = 0};
 enum Booleen2 variable2 = Faux2;
 printf("\nvariable2 = %d\n", variable2);
 typedef enum Booleen_t {Faux, Vrai} Booleen;
 Booleen variable = Faux;
 printf("\nvariable = %d\n", variable);
 printf("\n--------------------\n");
 printf(" Types incomplets\n");
 printf("--------------------\n");
 struct liste {
 struct liste * suivant;
 struct liste * precedant;
 void * element;
 /* C permet d'utiliser le type struct liste pour * suivant
 alors qu'il n'est pas encore complètement déclaré
 attention ceci est valable pour un pointeur uniquement */
 struct type_a {
 struct type_a * champ1;
 struct type_b * champ2;
 int champ3;
 struct type_b {
 struct type_a * ref;
 void * element;
 return 0;