Langage C : Résumé IIChapitre 7 - Compilation et modularisation7.1 Compilateur7.1.1 Phases de la compilation7.1.2 Prétraitement / pré-compilation7.1.3 Compilation7.1.4 Assemblage7.1.5 Edition de liens7.2 Outils gcc7.2.1 Etapes de compilation avec gcc7.2.2 Extensions de fichiers avec gcc7.2.3 Compilateur gcc7.3 Modularisation7.3.1 Compilation directe7.3.2 Construction de librairies7.3.3 Compilation avec librairies7.3.4 Edition de lien avec librairies7.3.5 Visibilité des variables (extern)7.3.6 Visibilité des fonctions7.3.7 Modularisation7.4 Outil make7.4.1 Règles de dépendances explicitesChapitre 8 - Tableaux, structures, ...8.1 Tableaux8.1.1 Description8.1.2 Déclaration8.1.3 Initialisation8.1.4 Tableau de valeurs constantes8.1.5 Taille des tableaux8.1.6 Tableaux multidimensionnelsDéclarationInitialisationAccès aux éléments8.2 Chaînes de caractères8.2.1 Définition8.2.2 Chaînes de caractères et I/O8.3 Structures8.3.1 Définition8.3.2 Syntaxe8.3.3 Déclaration de variables de type structureDéclarationDéclaration et initialisation8.3.4 Accès aux membresSyntaxe8.3.5 ImbricationsExemple8.3.6 Accès aux membres de structures imbriquéesExemple8.3.7 Affectation de structuresExemple8.3.8 Structures et fonctions8.4 typedef8.4.1 Type synonyme standardSyntaxeExemple 8.4.2 Type synonyme pour un tableauSyntaxe Exemple8.4.3 Type synonyme pour structureSyntaxe Exemple Chapitre 9 - Pointeurs9.1 Accès à la mémoire9.1.1 Adressage direct9.1.2 Adressage indirect9.2 Pointeurs9.2.1 Définition9.2.2 Adresse d'une variable9.2.3 Déclaration d'un pointeurSyntaxe9.2.4 Opérateur de déréférencement *9.2.5 Pointeurs particulierspointeur NULLType void*Chapitre 10 - Allocation dynamique10.1 Allocation statique vs dynamique10.1.1 Allocation mémoire statique et sizeof10.1.2 Allocation dynamique10.1.3 Fonction malloc10.1.4 Fonction free10.1.5 Fuite mémoire10.2 Allocation de variables en mémoire10.2.1 Organisation mémoireChapitre 11 - Pointeurs et fonctions II11.1 Pointeurs de pointeurs et arithmétique11.1.1 Pointeur de pointeur11.1.2 Arithmétique des pointeursAffectationAddition et soustraction entièreSoustraction de deux pointeurs11.2 Interaction d'un programme avec l'OS11.3 Passage de tableaux en paramètre11.3.1 Tableau unidimensionnel comme argument11.4 Pointeurs de fonctionsTableau de pointeurs de fonctionsRésumé11.5 CallbackExemples d'applicationChapitre 12 - Structures et types composés12.1 Champs de bitsDéclarationRemarques12.2 union12.3 enum12.4 typedef12.4.1 typedef struct12.4.2 typedef enum12.4.3 typedef de pointeur de fonctionChapitre 13 - structures de données dynamiques : listes chaînées13.1 Introduction13.2 Exercice complet main.cstructures.hnode.cnode.h
Le compilateur C de gcc est un programme qui transforme un fichier texte codé en langage C en un fichier exécutable.

Le fichier source subit des transformations purement textuelles.
Les directives de prétraitement commencent par #. (constantes, macros, ...)
La compilation traduit le fichier générer pendant le pré-traitement en un fichier texte contenant du code assembleur.
L'assemblage transforme le code assembleur en un fichier binaire, donc en instructions directement compréhensible par le processeur.
Un fichier est souvent séparé en plusieurs fichiers sources.
Une fois le code source assemblé, il faut donc lier entre les différents fichiers objets. L'édition de liens produit alors soit un fichier exécutable, soit une bibliothèque de fonctions.












extern)Le mot clé extern permet de déclarer la variable sans la définir, pour indiquer qu'elle est définie ailleurs.

Une fonction déclarée avec le mot-clé static n'est utilisable que dans le fichier où elle est déclarée.
Par défaut, une fonction est globale, elle est donc accessible depuis n'importe quelle fonction.

Le but de la modularisation est de pouvoir gérer des gros programmes informatiques avec de nombreux développeurs :
makeC'est un script basé sur des dépendances qui permet :
Le fichier makefile décrit :
Une règle de dépendance est composée de trois parties :
Syntaxe :

Un tableau est un ensemble d'éléments contigus en mémoire.
Tous les éléments sont de même type et sont référencés par un indice entier

xxxxxxxxxx11<type> <arrayName> [<size>], ... ;<type> : type des éléments du tableau (int, char, float, ...)<arrayName> : nom du tableau <size> : taille du tableau, càd le nombre d'éléments qu'il contiendra au maximum. Depuis C99, la taille d'un tableau peut être issu d'une variable (la taille du tableau n'est donc pas connue à la compilation mais uniquement à l'exécution).
On peut initialiser toutes les valeurs d'un tableau à 0 avec :
xxxxxxxxxx11int array [100] = {}; // les 100 éléments vaudront 0Il n'est pas possible d'affecter globalement tout le contenu d'un tableau à un autre avec un opérateur = !
Attention : le compilateur ne vérifie pas si les indices utilisés sont dans les limites du tableau. On peut alors lire ou modifier des cases mémoires qui n'appartiennent plus au tableau sans que le programme ne nous avertisse.
Il est permis de déclarer des tableaux de valeurs constantes en définissant le type des éléments par const.
Il est ensuite impossible de modifier les valeurs de ce tableau (les valeurs sont constantes).
Il est possible de connaitre la taille d'un tableau en bytes grâce à la fonction sizeof. On peut également l'utiliser pour connaitre le nombre de bytes que prend une variable ou un type.
xxxxxxxxxx31int data[100]; 2sizeof(data) // 4003sizeof(int) // 4xxxxxxxxxx11<type> <arrayName> [N1][N2]...[Ni]xxxxxxxxxx21int data [10][20]; // 2 dimensions2float values [10][20][30]; // 3 dimensionsxxxxxxxxxx11int matrix [2][4] = {{1,2,3,4}, {5,6,7,8}};xxxxxxxxxx21int values [2][3] = {{1,2,3}, {4,5,6}};2int nb = values[0][2] // nb = 3Une chaîne de caractères est un tableau unidimensionnel de type char, terminé par le caractère NULL (0x00, 0 ou '\0').
Exemple :
xxxxxxxxxx11char bonjour[] = {'b', 'o', 'n', 'j', 'o', 'u', 'r', '\0'};On peut aussi déclarer une chaîne constante avec des guillemets :
xxxxxxxxxx11char bonjour[] = "bonjour";printf et scanf supportent la saisie et l'affichage de chaînes de caractères avec la spécification de format %s.
printf affiche tous les caractères de la chaîne jusqu'au caractère de fin (exclu).
scanf("%s", ...) récupère tous les caractères saisis jusqu'au premier séparateur (espace ou fin de ligne). '\0' va automatiquement être ajouté à la fin de la chaîne.
Les structures permettent de désigner sous un seul nom, un ensemble de valeurs pouvant être de type différents.
xxxxxxxxxx71struct <id> 2{3 <type1> <n1>;4 <type2> <n2>;5 ...6 <typeN> <nN>;7}<var1>, ... ;<id> ou <var> sont optionnels (l'un ou l'autre).

xxxxxxxxxx11struct PatientFile client1;xxxxxxxxxx51struct PatienFile2 client1 = {"Jules", 25, 176, 72},3 client2 = {"Eva", 18},4 client3 = {"Charles", .weight=87};5
On accède à un membre d'une variable de type structure avec l'opérateur point .
xxxxxxxxxx11<variable>.<membre>xxxxxxxxxx31struct PatientFile myPatient;2myPatient.height = 190.0;3strcpy(myPatient.name, "Edgar");
xxxxxxxxxx111struct Date2{3 short day, month, year;4};5
6struc PatientFileB7{8 char name[32];9 struct Date birthDate;10 float height;11};xxxxxxxxxx11struct PatientFileB marc = {"Marc", {01,01,1990}, 190.0};xxxxxxxxxx31struct PatientFileB jeanne;2jeanne.height = 173.5;3jeanne.birthDate.year = 1992;Possible uniquement si les deux structures sont de même type.
xxxxxxxxxx41struct PatientFile jean = {"Jean", ...};2struct PatientFile marc;3
4marc = jean; // tout le contenu est recopiéUne fonction peut retourner un résultat de type structure ou un pointeur sur une structure :
xxxxxxxxxx11struct MyStruct function1(...);Une structure peut également être passée comme paramètre à une fonction :
xxxxxxxxxx41void function1(struct MyStruct s1)2{3 ...4}typedefLe langage C permet de renommer des types en leur donnant un synonyme.
L'intérêt est de simplifier l'écriture et la lecture du code ainsi que pour favoriser la portabilité du code.
Pour déclarer un nouveau type, on utilise le mot-clé typedef.
xxxxxxxxxx11typedef <type std> <synonyme>;xxxxxxxxxx51typedef unsigned int uint32;2typedef unsigned char uint8;3
4uint32 n1 = 423; // équivalent à "unsigned int n1 = 3;"5uint8 n2 = 123; //...xxxxxxxxxx11typedef <type std> <synonyme> [<n>];xxxxxxxxxx21typedef char str[80];2str s1; // équivalent à " char str[80]; "xxxxxxxxxx11typedef <déf structure> <synonyme>;xxxxxxxxxx51typedef struct2{3 int x, y;4} Coord;5Coord pt1;

On accède au contenu d'une variable par le nom de la variable.
xxxxxxxxxx11printf("%d", x); // x vaut 40On accède au contenu d'une variable par son adresse ou par un pointeur (variable qui contient l'adresse).
xxxxxxxxxx11printf("%d", *3F06); // 40 est contenu dans la case 3F06Les pointeurs sont des variables qui mémorisent les adresse physiques de la mémoire. Cela nous permet d'accéder à un emplacement mémoire.
Une variable permet d'accéder à un emplacement mémoire.
Un pointeur permet d'accéder à n'importe quel emplacement mémoire. C'est une variable qui contient l'adresse d'une autre variable.
L'adresse d'une certaine variable est obtenue avec l'opérateur &.
xxxxxxxxxx11&x // ceci représente l'adresse de la variable xxxxxxxxxxx11<type> *<variable>;Le type correspond au type de la case mémoire pointée. On utilise le symbole * pour le différencier d'une variable ordinaire lors de sa déclaration.
*Soit un pointeur px pointant sur la variable x. On peut afficher la valeur de la variable x à partir du pointeur déréférencé :
xxxxxxxxxx11printf("%d", *px); // on affiche le contenu de x On peut également écrire des valeurs dans x de cette manière :
xxxxxxxxxx11*px = 20; // x vaudra maintenant 20 NULLOn utilise la constante NULL pour indiquer qu'un pointeur ne contient pas encore d'adresse valide. Dans l'idéal, il faudrait toujours initialiser les pointeurs à la constante NULL.
xxxxxxxxxx11int *px = NULL;void*Ce type est utilisé quand on ne sait pas encore le type sur lequel va pointer le pointeur.
xxxxxxxxxx11void *p; sizeofOn cherche la taille que prend une certaine variable ou constante en mémoire.
Pour ça, on utilise l'opérateur unaire sizeof.
L'opérateur sizeof donne, en octets :
Syntaxe :
xxxxxxxxxx31sizeof <var>2sizeof <const>3sizeof(<type>)Si on ne connait pas la taille des données avant l'exécution, on peut réserver une place suffisante en mémoire. Or, on est presque sûr qu'une partie de cette place réservée ne sera jamais utilisée.
Pour éviter ça, le programmeur peut lui-même gérer l'allocation mémoire en fonction des besoins au moment de l'exécution, avec un mécanisme d'allocation dynamique.
mallocLa fonction malloc réserve dynamiquement de la mémoire lors de l'exécution du programme.
Syntaxe :
xxxxxxxxxx11void* malloc(int N)N nombre de bytes à réserver.
retour : adresse de type void* d'un bloc mémoire de N octets (réservé pour nous). Si espace insuffisant : renvoie NULL
freeLa fonction free libère la mémoire précédemment réservée avec malloc.
Syntaxe :
xxxxxxxxxx11free(<adress>)La mémoire est libérée automatiquement à la fin du programme, même si la fonction free n'a pas été utilisée.
Si on perd l'adresse d'un bloc de mémoire qui nous a été alloué (avec malloc) sans l'avoir préalablement libéré avec free, on parle de fuite mémoire.
Bonne pratique : affecter la valeur NULL au pointeur immédiatement après avoir libéré la mémoire.

Soit une variable a. *ptr_a = &a est un pointeur sur la variable a. ptr_a contient alors la valeur de l'adresse de la variable a. Or, cette valeur est elle aussi stockée quelque part dans la mémoire, précisément à l'adresse ptr_b = &ptr_a. La valeur stockée en a est alors accessible par a ou par *ptr_a ou par **ptr_b. **ptr_b est un pointeur double sur a.

Soient ptr1 et ptr2 deux pointeurs sur le même type de données, alors ptr1 = ptr2 fait pointer ptr1 sur le même "objet" que ptr2
Si ptr pointe sur l'élément a[i] d'un tableau, alors :
ptr+n pointe sur a[i+n]ptr-n pointe sur a[i-n]Soient ptr1 et ptr2 deux pointeurs pointant sur le même tableau.
ptr2-ptr1 fournit le nombre d'éléments compris entre ptr1 et ptr2.
Si le résultat de la soustraction est :
ptr1 précède ptr2ptr1 = ptr2ptr2 précède ptr1La fonction main() :
renvoie une valeur avec return
reçoit des valeurs de l'OS avec :
int main (int argc, char **argv);Passer un tableau en argument correspond en réalité à passer une copie de son adresse.
xxxxxxxxxx21float tab[4] = {1, 2, 3, 4};2function(tab);L'argument est donc un pointeur sur une variable de type float :
xxxxxxxxxx31void function(float *array)2// ou 3void function (float array[])En C, comme pour les variables, chaque fonction possède une adresse définie. On peut alors mémoriser cette dernière dans une variable (pointeur) qui contiendra l'adresse de la fonction :
xxxxxxxxxx11<type> (* ptrSurFonction) (<args>);Exemple :


xxxxxxxxxx11<type> (* tabPtrFonctions [taille]) (<args>);

Une fonction de rappel [callback] est une fonction qui est passée en argument à une autre fonction. Cette dernière peut alors faire usage de cette fonction de rappel comme de n'importe quelle autre fonction, alors qu'elle ne la connait pas par avance. (Wikipédia)


Structure particulière qui permet de compacter et d'aligner les données. Chaque membre de la structure est défini comme un champ en précisant à la fin de sa déclaration le nombre de bits qu'il occupe.
xxxxxxxxxx61struct <name>2{3 <type1> <member1> : <nb_bits> ;4 <type2> <member2> : <nb_bits> ;5 ....6};unionLes unions permettent de stocker des objets de type différents dans un même espace mémoire. Cela signifie que l'on ne peut pas sauvegarder deux informations de types différents en même temps !
xxxxxxxxxx61union <name>2{3 <type1> <member1>;4 <type2> <member2>;5 ...6};La taille d'un variable de type union est fixe ; elle est égale à la taille nécessaire pour stocker le membre le plus grand.
enumLes énumérations permettent d'attribuer un identificateur à un entier grâce au déclarateur d'énumération enum.
xxxxxxxxxx61enum <name>2{3 <id1> [= val1],4 <id2> [= val2],5 ...6};typedefLe langage C permet de renommer des types en leur donnant un synonyme avec le mot-clé typedef.
typedef structxxxxxxxxxx11typedef struct {<définition de la structure>} <type_name> ;typedef enumxxxxxxxxxx11typedef enum {<enumeration>} <enum_name>;typedef de pointeur de fonctionxxxxxxxxxx11typedef <type_retour> (*<type_name>) (<arg_type_1>, <arg_type_2, ...);Les listes chaînées répondent au problème que l'on rencontre quand la taille, la quantité ou l'organisation des données varie au cours du temps et qu'il n'est pas possible de les prédire au moment de la compilation.
On va alors utiliser des structures dynamiques.
Le principe de base est que chaque membre de la liste est le successeur ou le prédécesseur d'un autre membre de cette même liste. Pour cela, on utilisera des pointeurs sur des structures. De cette manière, il devient possible d'accéder à un membre à partir d'un autre.
Il existe plusieurs types de structures dynamiques :

main.cxxxxxxxxxx3612345
678
9int main(int argc, char *argv[])10{11 /* your code here ... */12
13 node *head = ask_numbers_and_get_head();14
15 if (head == NULL)16 {17 printf("\nEmpty list !\n");18 return 1;19 }20
21 printf("\n\ncontenu de la liste chainee : \n");22 print_node(head);23
24 inc_node(head);25
26 printf("\n\nchaque element de la liste chainee incremente de 1 : \n");27 print_node(head);28
29 printf("\n\n");30 free_node(head);31
32 /* end of the program */33 printf("\n\n----- program finished-------\n\n");34 system("PAUSE");35 return 0;36}structures.hxxxxxxxxxx12123
4struct node5{6 int nb;7 struct node *next;8};9
10typedef struct node node;11
12node.cxxxxxxxxxx81123456
7node *ask_numbers_and_get_head(void)8{9 node *head = NULL; /* initialisation of pointer on node structure at NULL*/10 bool toExit = false; /*variable to determine if we have to stop asking user for more numbers. True if we have to stop, false if not */11
12 printf("Entrez des nombres, separes par ENTER, puis entrez 0 pour terminer\n\n");13
14 do15 {16 static int i = 0; /* index variable to show values count */17 node *element = (node *)malloc(sizeof(node)); /* creates a new element that will contain the number entered by user */18 element->nb = 0; /* initializes the number variable of the element at 0 */19 element->next = NULL; /* initializes the pointer on next element at NULL */20
21 printf("valeur %d : ", ++i); /* displays prompt */22 scanf("%d", &(element->nb)); /* store input in current element */23
24 if (element->nb != 0) /* if the user has not entered a 0 */25 {26 add_node(&head, element); /* adds the current element to the list */27 }28 else29 {30 free(element); /* frees the element we just created, it only contains the 0 value */31 toExit = true; /* set toExit variable to true because we want to exit the do while loop */32 }33
34 } while (!toExit); /* continues loop while toExit variable is not set to false */35
36 return head; /* return the pointer on the first element of the list we just created */37}38
39void inc_node(node *current)40{41 if (current->next != NULL) /* if the next element exists */42 {43 inc_node(current->next); /* calls the same function we are in */44 }45 ++current->nb; /* increments the number value of current element */46}47
48void add_node(node **head, node *new)49{50 if (*head == NULL) /* if we are creating the first element of the list */51 {52 *head = new; /* the head is the element previously created */53 (*head)->next = NULL; /* the next element does not exist yet */54 }55 else /* for all other elements */56 {57 new->next = *head; /* the element after the element we just created is the previous head */58 *head = new; /* the head is now the element we just created */59 }60}61
62void print_node(node *current)63{64 printf("\n0x%p : %d", current, current->nb); /* prints the content of the current element with its address */65
66 if (current->next != NULL) /* if next element exits */67 {68 print_node(current->next); /* calls the function we are in to print next element */69 }70}71
72void free_node(node *current)73{74 if (current->next != NULL) /* if next element exists */75 {76 free_node(current->next); /* calls the same function we are in to free next element first */77 }78
79 printf("\nfreeing 0x%p", current);80 free(current); /* frees the last element of the list */81}node.h37123
45
6/* 7 Recursive function that prints the specified list. The function ends when current->next is NULL. 8 node *current : pointer on node structure where to begin the printing9*/10void print_node(node *current);11
12/* 13 Recursive function that frees the specified list. The function ends when current->next is NULL.14 node *current : pointer on node structure where to begin the memory freeing15*/16void free_node(node *current);17
18/*19 This function adds a node at the beginning of the specified list20 node **head : pointer on a pointer on node structure where the current head is. 21 node * new : pointer on a node structure that we want to add at the beginning of the list. 22*/23void add_node(node **head, node *new);24
25/*26 Recursive function that increments all the numeric members of the list (i.e. current->nb). The function ends when current->next is NULL.27 node *current : pointer on node structure where to begin the incrementation28*/29void inc_node(node *current);30
31/*32 This function asks user to enter a set of integer numbers. It stops for asking when the user enters a 0. 33 The function returns a pointer on node structure that is the head of a linked list, containing all the numbers entered by the user. 34*/35node *ask_numbers_and_get_head(void);36
37