Programmation C source/types avancés

Un livre de Wikilivres.
Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
#include <stdio.h>
/* inclut stdio.h qui décrit
   - la fonction printf */

#include <stddef.h>
/* inclut stddef.h qui décrit
   - la fonction offsetof */

#include <string.h>
/* inclut string.h qui décrit
   - la fonction memcmp */

#include <stdlib.h>
/* 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;
}