Langage C : Résumé II

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

Chapitre 7 - Compilation et modularisation

7.1 Compilateur

7.1.1 Phases de la compilation

Le compilateur C de gcc est un programme qui transforme un fichier texte codé en langage C en un fichier exécutable.

image-20211201082941081

7.1.2 Prétraitement / pré-compilation

Le fichier source subit des transformations purement textuelles.

Les directives de prétraitement commencent par #. (constantes, macros, ...)

7.1.3 Compilation

La compilation traduit le fichier générer pendant le pré-traitement en un fichier texte contenant du code assembleur.

7.1.4 Assemblage

L'assemblage transforme le code assembleur en un fichier binaire, donc en instructions directement compréhensible par le processeur.

7.1.5 Edition de liens

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.

7.2 Outils gcc

7.2.1 Etapes de compilation avec gcc

image-20211201083830107

7.2.2 Extensions de fichiers avec gcc

image-20211201083925051

7.2.3 Compilateur gcc

image-20211201084006543

image-20211201084035813

image-20211201084210382

7.3 Modularisation

image-20211201084315293

image-20211201084447606

image-20211201084502765

7.3.1 Compilation directe

image-20211201084512615

7.3.2 Construction de librairies

image-20211201084614903

7.3.3 Compilation avec librairies

image-20211201084933577

7.3.4 Edition de lien avec librairies

image-20211201085041629

7.3.5 Visibilité des variables (extern)

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

image-20211201085608908

7.3.6 Visibilité des fonctions

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.

7.3.7 Modularisation

image-20211201085914344

Le but de la modularisation est de pouvoir gérer des gros programmes informatiques avec de nombreux développeurs :

7.4 Outil make

C'est un script basé sur des dépendances qui permet :

Le fichier makefile décrit :

  1. Les dépendances qu'il y a entre les fichiers intervenants dans la construction d'un exécutable
  2. Les commandes (gcc, OS, ou autres) à lancer pour construire l'exécutable.

7.4.1 Règles de dépendances explicites

Une règle de dépendance est composée de trois parties :

  1. Une cible (target)
  2. Une liste de dépendances (sous-buts)
  3. Des commandes de mise à jour

Syntaxe :

image-20211201091100412

Chapitre 8 - Tableaux, structures, ...

8.1 Tableaux

8.1.1 Description

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 i[0,n1]n est le nombre total d'éléments du tableau.

image-20211117082603302

8.1.2 Déclaration

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).

8.1.3 Initialisation

On peut initialiser toutes les valeurs d'un tableau à 0 avec :

Il 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.

8.1.4 Tableau de valeurs constantes

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).

8.1.5 Taille des tableaux

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.

8.1.6 Tableaux multidimensionnels

Déclaration

Initialisation

Accès aux éléments

8.2 Chaînes de caractères

8.2.1 Définition

Une chaîne de caractères est un tableau unidimensionnel de type char, terminé par le caractère NULL (0x00, 0 ou '\0').

Exemple :

On peut aussi déclarer une chaîne constante avec des guillemets :

8.2.2 Chaînes de caractères et I/O

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.

8.3 Structures

8.3.1 Définition

Les structures permettent de désigner sous un seul nom, un ensemble de valeurs pouvant être de type différents.

8.3.2 Syntaxe

<id> ou <var> sont optionnels (l'un ou l'autre).

image-20211117094113141

8.3.3 Déclaration de variables de type structure

Déclaration

Déclaration et initialisation

8.3.4 Accès aux membres

On accède à un membre d'une variable de type structure avec l'opérateur point .

Syntaxe

image-20211201092459762

8.3.5 Imbrications

Exemple

8.3.6 Accès aux membres de structures imbriquées

Exemple

8.3.7 Affectation de structures

Possible uniquement si les deux structures sont de même type.

Exemple

8.3.8 Structures et fonctions

Une fonction peut retourner un résultat de type structure ou un pointeur sur une structure :

Une structure peut également être passée comme paramètre à une fonction :

8.4 typedef

Le 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.

8.4.1 Type synonyme standard

Syntaxe

Exemple

8.4.2 Type synonyme pour un tableau

Syntaxe

Exemple

8.4.3 Type synonyme pour structure

Syntaxe

Exemple

Chapitre 9 - Pointeurs

9.1 Accès à la mémoire

image-20211208081833561

image-20211208081936607

9.1.1 Adressage direct

On accède au contenu d'une variable par le nom de la variable.

9.1.2 Adressage indirect

On accède au contenu d'une variable par son adresse ou par un pointeur (variable qui contient l'adresse).

9.2 Pointeurs

9.2.1 Définition

Les 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.

9.2.2 Adresse d'une variable

L'adresse d'une certaine variable est obtenue avec l'opérateur &.

9.2.3 Déclaration d'un pointeur

Syntaxe

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.

9.2.4 Opérateur de déréférencement *

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é :

On peut également écrire des valeurs dans x de cette manière :

9.2.5 Pointeurs particuliers

pointeur NULL

On 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.

Type void*

Ce type est utilisé quand on ne sait pas encore le type sur lequel va pointer le pointeur.

Chapitre 10 - Allocation dynamique

10.1 Allocation statique vs dynamique

10.1.1 Allocation mémoire statique et sizeof

On 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 :

10.1.2 Allocation dynamique

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.

10.1.3 Fonction malloc

La fonction malloc réserve dynamiquement de la mémoire lors de l'exécution du programme.

Syntaxe :

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

10.1.4 Fonction free

La fonction free libère la mémoire précédemment réservée avec malloc.

Syntaxe :

La mémoire est libérée automatiquement à la fin du programme, même si la fonction free n'a pas été utilisée.

10.1.5 Fuite mémoire

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.

10.2 Allocation de variables en mémoire

10.2.1 Organisation mémoire

image-20211215084516524

Chapitre 11 - Pointeurs et fonctions II

11.1 Pointeurs de pointeurs et arithmétique

11.1.1 Pointeur de pointeur

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.

image-20220110200521843

11.1.2 Arithmétique des pointeurs

Affectation

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

Addition et soustraction entière

Si ptr pointe sur l'élément a[i] d'un tableau, alors :

Soustraction de deux pointeurs

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 :

11.2 Interaction d'un programme avec l'OS

La fonction main() :

11.3 Passage de tableaux en paramètre

11.3.1 Tableau unidimensionnel comme argument

Passer un tableau en argument correspond en réalité à passer une copie de son adresse.

L'argument est donc un pointeur sur une variable de type float :

11.4 Pointeurs de fonctions

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 :

Exemple :

image-20220110202432350

image-20220110202500924

Tableau de pointeurs de fonctions

Résumé

image-20220119083417508

image-20220119083434224

11.5 Callback

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)

Exemples d'application

image-20220119083806325

Chapitre 12 - Structures et types composés

image-20220119084138917

12.1 Champs de bits

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.

Déclaration

Remarques

  1. Les types des champs doivent être entiers
  2. Il n'est pas possible de prendre l'adresse d'un champ
  3. Les exécutables contenant des champs de bits ne sont pas portables

12.2 union

Les 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 !

La taille d'un variable de type union est fixe ; elle est égale à la taille nécessaire pour stocker le membre le plus grand.

12.3 enum

Les énumérations permettent d'attribuer un identificateur à un entier grâce au déclarateur d'énumération enum.

12.4 typedef

Le langage C permet de renommer des types en leur donnant un synonyme avec le mot-clé typedef.

12.4.1 typedef struct

12.4.2 typedef enum

12.4.3 typedef de pointeur de fonction

Chapitre 13 - structures de données dynamiques : listes chaînées

13.1 Introduction

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 :

image-20220119091024639

13.2 Exercice complet

main.c

structures.h

node.c

node.h