Cours C
Cours C
Cours C
(Perfectionnement à la programmation en C)
Samuele Giraudo
1 / 399
Introduction
L’objectif de ce module est d’approfondir les concepts de base et d’approcher certains concepts
plus avancés de la programmation en C.
2 / 399
Contenu du cours
3 / 399
Pré-requis
4 / 399
Quelques dates
Le langage C est apparu en 1972 dans les laboratoires Bell. Il a été créé par D. Ritchie et K.
Thompson au même moment que l’apparition des systèmes UNIX.
5 / 399
Quelques dates
Le langage C est apparu en 1972 dans les laboratoires Bell. Il a été créé par D. Ritchie et K.
Thompson au même moment que l’apparition des systèmes UNIX.
Il est influencé par le langage B qui, contrairement au C, ne possédait pas de système de type.
5 / 399
Quelques dates
Le langage C est apparu en 1972 dans les laboratoires Bell. Il a été créé par D. Ritchie et K.
Thompson au même moment que l’apparition des systèmes UNIX.
Il est influencé par le langage B qui, contrairement au C, ne possédait pas de système de type.
Quelques dates sur l’évolution de la spécification du langage :
I 1978 : première description complète du langage (C traditionnel) ;
I 1989 : publication de la norme ANSI C (ou C89) ;
I 1995 : évolution du langage C94/95 ;
I 1999 : évolution du langage C99 ;
I 2011 : nouvelle version du standard C11.
5 / 399
Quelques dates
Le langage C est apparu en 1972 dans les laboratoires Bell. Il a été créé par D. Ritchie et K.
Thompson au même moment que l’apparition des systèmes UNIX.
Il est influencé par le langage B qui, contrairement au C, ne possédait pas de système de type.
Quelques dates sur l’évolution de la spécification du langage :
I 1978 : première description complète du langage (C traditionnel) ;
I 1989 : publication de la norme ANSI C (ou C89) ;
I 1995 : évolution du langage C94/95 ;
I 1999 : évolution du langage C99 ;
I 2011 : nouvelle version du standard C11.
5 / 399
Quelques caractéristiques
6 / 399
Quelques caractéristiques
6 / 399
Quelques caractéristiques
6 / 399
Quelques caractéristiques
L’un de ses compilateurs, gcc, fruit de nombreuses optimisations, fait que le C est un langage
d’une efficacité extrême.
6 / 399
Axe 1 : écrire un projet maintenable
Bases
Habitudes
Modules
Compilation
7 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
8 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
9 / 399
La notion de programme
Un programme en C est avant tout un texte (contenu dans un fichier) qui suit certaines règles
(dites de syntaxe).
10 / 399
La notion de programme
Un programme en C est avant tout un texte (contenu dans un fichier) qui suit certaines règles
(dites de syntaxe).
10 / 399
La notion de programme
Un programme en C est avant tout un texte (contenu dans un fichier) qui suit certaines règles
(dites de syntaxe).
Tout programme possède une fonction principale nommée main . C’est par elle que commence
l’exécution du programme. On appelle ceci le point d’entrée du programme.
10 / 399
Compiler un programme
Compiler un programme signifie traduire le fichier le contenant en un langage compréhensible
et exécutable par la machine cible.
11 / 399
Compiler un programme
Compiler un programme signifie traduire le fichier le contenant en un langage compréhensible
et exécutable par la machine cible.
On compile un fichier Prog.c par la commande
11 / 399
Compiler un programme
Compiler un programme signifie traduire le fichier le contenant en un langage compréhensible
et exécutable par la machine cible.
On compile un fichier Prog.c par la commande
11 / 399
Compiler un programme
Compiler un programme signifie traduire le fichier le contenant en un langage compréhensible
et exécutable par la machine cible.
On compile un fichier Prog.c par la commande
11 / 399
Compiler un programme
Compiler un programme signifie traduire le fichier le contenant en un langage compréhensible
et exécutable par la machine cible.
On compile un fichier Prog.c par la commande
11 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
12 / 399
Expressions
13 / 399
Expressions
13 / 399
Expressions
13 / 399
Expressions
13 / 399
Expressions
13 / 399
Expressions
Toute expression possède une valeur et un type. Le processus qui consiste à déterminer la valeur
d’une expression se nomme l’évaluation.
13 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0
I ’a’
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0
I ’a’
I "abc"
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0
I ’a’
I "abc"
I x
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0
I ’a’
I "abc"
I x
I a == 2
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0 I b = 3
I ’a’
I "abc"
I x
I a == 2
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0 I b = 3
I f(5)
I ’a’
I x
I a == 2
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0 I b = 3
I f(5)
I ’a’
I a == 2
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0 I b = 3
I f(5)
I ’a’
14 / 399
Expressions
P.ex., les entités suivantes sont des expressions :
I 0 I b = 3
I f(5)
I ’a’
14 / 399
Instructions
Une instruction est définie récursivement comme étant soit :
1. une expression E; terminée par un point-virgule ;
15 / 399
Instructions
Une instruction est définie récursivement comme étant soit :
1. une expression E; terminée par un point-virgule ;
À la différence des expressions, c’est souvent l’effet d’une instruction qui est sa raison d’être (et
non plus uniquement sa valeur ou son type).
15 / 399
Instructions
Une instruction est définie récursivement comme étant soit :
1. une expression E; terminée par un point-virgule ;
À la différence des expressions, c’est souvent l’effet d’une instruction qui est sa raison d’être (et
non plus uniquement sa valeur ou son type).
En effet, une instruction peut p.ex. afficher un élément, allouer une zone mémoire, lire dans un
fichier, etc. La valeur de l’expression sous-jacente à l’instruction est d’importance secondaire.
15 / 399
Instructions
P.ex., les entités suivantes sont des instructions :
I 1; I int a;
I a = b; I int tab[64];
I printf("abc\n"); I tab[3];
16 / 399
Expressions à effet de bord
Une expression est à effet de bord (ou encore à effet secondaire) si son évaluation modifie la
mémoire.
17 / 399
Expressions à effet de bord
Une expression est à effet de bord (ou encore à effet secondaire) si son évaluation modifie la
mémoire.
P.ex.,
17 / 399
Expressions à effet de bord
Une expression est à effet de bord (ou encore à effet secondaire) si son évaluation modifie la
mémoire.
P.ex.,
En revanche,
I a = 31 , I char c ,
I printf("abc\n") ,
I malloc(64) ,
sont des expressions à effet de bord (la déclaration de variable n’est pas une expression mais une
instruction).
17 / 399
Expressions à effet de bord
En règle générale, ce sont les éléments suivants dans les expressions qui produisent des effets de
bord :
I les affectations ;
I les allocations de mémoire ;
I la sollicitation au système de fichiers.
18 / 399
Expressions à effet de bord
En règle générale, ce sont les éléments suivants dans les expressions qui produisent des effets de
bord :
I les affectations ;
I les allocations de mémoire ;
I la sollicitation au système de fichiers.
En revanche, les éléments suivants dans les expressions ne produisent pas d’effet de bord :
I lectures de constantes et de variables ;
I calculs arithmétiques, logiques ou bit à bit ;
I comparaisons.
18 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
19 / 399
Blocs
Un bloc est une suite d’instructions délimitée par des accolades. Il est constitué d’une partie
consacrée à la déclaration de variables et d’une partie consacrée aux instructions.
20 / 399
Blocs
Un bloc est une suite d’instructions délimitée par des accolades. Il est constitué d’une partie
consacrée à la déclaration de variables et d’une partie consacrée aux instructions.
20 / 399
Blocs
Un bloc est une suite d’instructions délimitée par des accolades. Il est constitué d’une partie
consacrée à la déclaration de variables et d’une partie consacrée aux instructions.
20 / 399
Blocs
Un bloc est une suite d’instructions délimitée par des accolades. Il est constitué d’une partie
consacrée à la déclaration de variables et d’une partie consacrée aux instructions.
Sachant qu’un bloc est une suite d’instructions, il est possible de placer un bloc dans la section
d’instructions d’un bloc et ainsi d’imbriquer plusieurs blocs.
20 / 399
Blocs
int main() { La partie grise est un bloc au sein d’une
int b;
scanf("%d", &b); fonction main .
{
int a;
a = 1 + b;
}
Il y a des règles concernant la visibilité des
} variables (que nous verrons plus loin).
21 / 399
Blocs
int main() { La partie grise est un bloc au sein d’une
int b;
scanf("%d", &b); fonction main .
{
int a;
a = 1 + b;
}
Il y a des règles concernant la visibilité des
} variables (que nous verrons plus loin).
21 / 399
Opérateur de test if
L’opérateur de test se décline en deux versions :
22 / 399
Opérateur de test if
L’opérateur de test se décline en deux versions :
Diagrammes d’exécution :
Debut Debut
E E
6= 0 =0
6= 0
=0
B B_1 B_2
Fin
Fin
22 / 399
Instruction de branchement switch
23 / 399
Instruction de branchement switch
L’exécution de cette instruction se passe ainsi. Soit i le plus petit entier (s’il existe) tel que les
évaluations de E et E_i sont égales. Les instructions I_i , . . ., I_N ainsi que I_D sont exécutées.
23 / 399
Instruction de branchement switch
L’exécution de cette instruction se passe ainsi. Soit i le plus petit entier (s’il existe) tel que les
évaluations de E et E_i sont égales. Les instructions I_i , . . ., I_N ainsi que I_D sont exécutées.
La ligne default : I_D est facultative. Si elle est présente, I_D est toujours exécutée.
23 / 399
Instruction de branchement switch
Ceci permet de n’exécuter que le I_i tel que les évaluations de E et E_i sont égales. Dans ce
cas, I_D n’est exécuté que si aucun des E_i ne l’a été.
E
= E_1 6= E_i , 1 6 i 6 N
= E_2 = E_N
Fin
24 / 399
Instruction itérative while
25 / 399
Instruction itérative while
Diagramme d’exécution :
Debut
6= 0 E =0
B Fin
25 / 399
Instruction itérative do while
26 / 399
Instruction itérative do while
Diagramme d’exécution :
Debut
6= 0
=0
Fin
26 / 399
Instruction itérative for
L’instruction itérative for admet la syntaxe
for (E_1 ; E_2 ; E_3) Ici, E_2 est une expression booléenne (le test), E_1
B
(l’initialisation) et E_3 (l’incrémentation) sont des
expressions et B est un bloc d’instructions.
27 / 399
Instruction itérative for
L’instruction itérative for admet la syntaxe
for (E_1 ; E_2 ; E_3) Ici, E_2 est une expression booléenne (le test), E_1
B
(l’initialisation) et E_3 (l’incrémentation) sont des
expressions et B est un bloc d’instructions.
Diagramme d’exécution :
Debut
E_1
E_2
6= 0
=0
B
Fin E_3
27 / 399
Instruction itérative for
28 / 399
Instruction itérative for
28 / 399
Instruction itérative for
for ( ; *str != ’\0’; str++)) { Affiche la chaîne de caractères str . Il n’y a pas
putchar(*str);
} d’initialisation. L’incrémentation fait pointer str sur le
prochain caractère.
28 / 399
Instruction itérative for
for ( ; *str != ’\0’; str++)) { Affiche la chaîne de caractères str . Il n’y a pas
putchar(*str);
} d’initialisation. L’incrémentation fait pointer str sur le
prochain caractère.
28 / 399
Instructions de court-circuit
29 / 399
Instructions de court-circuit
L’instruction break permet à l’exécution de sortir d’une structure switch , while , do while ou
for . L’exécution continue alors aux instructions qui suivent cette structure.
29 / 399
Instructions de court-circuit
L’instruction break permet à l’exécution de sortir d’une structure switch , while , do while ou
for . L’exécution continue alors aux instructions qui suivent cette structure.
L’instruction continue permet à l’exécution de sauter à la fin du bloc B d’une structure while ,
do while ou for . L’exécution continue alors à l’évaluation de l’expression test.
29 / 399
Instructions de court-circuit
Dans une boucle for , l’instruction continue fait que l’expression d’incrémentation est tout de
même évaluée.
30 / 399
Instructions de court-circuit
Dans une boucle for , l’instruction continue fait que l’expression d’incrémentation est tout de
même évaluée.
30 / 399
Instructions de court-circuit
Dans une boucle for , l’instruction continue fait que l’expression d’incrémentation est tout de
même évaluée.
30 / 399
Instructions de court-circuit
Dans une boucle for , l’instruction continue fait que l’expression d’incrémentation est tout de
même évaluée.
30 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
31 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
1. un identificateur ;
Identificateur
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
1. un identificateur ;
2. un type ;
Type
Identificateur
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
1. un identificateur ;
2. un type ;
Valeur
3. une valeur ; Type
Identificateur
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
1. un identificateur ;
2. un type ;
Valeur
3. une valeur ; Adresse Type
4. une adresse ; Identificateur
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
32 / 399
Variables
Une variable est une entité constituée des cinq éléments suivants :
Intuitivement, c’est une boîte qui peut contenir un objet (valeur) et qui dispose d’un nom
(identificateur).
Une boîte ne peut contenir que des objets d’une certaine sorte (type).
Elle se situe de plus à un endroit bien précis dans la mémoire (adresse) et elle n’est visible qu’à
partir de certains endroits du code (portée lexicale).
32 / 399
Identificateurs de variable
L’identificateur d’une variable est un mot commençant par une lettre ou bien ’_’ , suivi par un
nombre arbitraire de lettres, chiffres ou ’_’ .
De plus, aucun identificateur ne peut-être un mot réservé du langage. En voici la liste complète :
auto break case char const continue default do
33 / 399
Identificateurs de variable
L’identificateur d’une variable est un mot commençant par une lettre ou bien ’_’ , suivi par un
nombre arbitraire de lettres, chiffres ou ’_’ .
De plus, aucun identificateur ne peut-être un mot réservé du langage. En voici la liste complète :
auto break case char const continue default do
33 / 399
Valeur d’une variable
La valeur d’une variable est la raison pour laquelle celle-ci existe. Le rôle premier d’une variable
étant en effet de contenir une valeur.
34 / 399
Valeur d’une variable
La valeur d’une variable est la raison pour laquelle celle-ci existe. Le rôle premier d’une variable
étant en effet de contenir une valeur.
La valeur d’une variable n’est pas attribuée à sa déclaration (elle contient à ce moment là une
valeur mais il ne faut rien supposer dessus).
34 / 399
Valeur d’une variable
La valeur d’une variable est la raison pour laquelle celle-ci existe. Le rôle premier d’une variable
étant en effet de contenir une valeur.
La valeur d’une variable n’est pas attribuée à sa déclaration (elle contient à ce moment là une
valeur mais il ne faut rien supposer dessus).
On accède à la valeur d’une variable par son identificateur.
34 / 399
Valeur d’une variable
La valeur d’une variable est la raison pour laquelle celle-ci existe. Le rôle premier d’une variable
étant en effet de contenir une valeur.
La valeur d’une variable n’est pas attribuée à sa déclaration (elle contient à ce moment là une
valeur mais il ne faut rien supposer dessus).
On accède à la valeur d’une variable par son identificateur.
34 / 399
Valeur d’une variable
La valeur d’une variable est la raison pour laquelle celle-ci existe. Le rôle premier d’une variable
étant en effet de contenir une valeur.
La valeur d’une variable n’est pas attribuée à sa déclaration (elle contient à ce moment là une
valeur mais il ne faut rien supposer dessus).
On accède à la valeur d’une variable par son identificateur.
34 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
Une L-value est une expression qui peut se situer dans le membre gauche d’une affectation
(l’expression peut recevoir une valeur).
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
Une L-value est une expression qui peut se situer dans le membre gauche d’une affectation
(l’expression peut recevoir une valeur).
Une R-value est une expression qui peut se situer dans le membre droit d’une affectation (une
valeur peut être lue depuis l’expression).
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
Une L-value est une expression qui peut se situer dans le membre gauche d’une affectation
(l’expression peut recevoir une valeur).
Une R-value est une expression qui peut se situer dans le membre droit d’une affectation (une
valeur peut être lue depuis l’expression).
Note 1 : c’est le contexte qui permet de dire si une expression est une L-value ou une R-value.
35 / 399
L-values et R-values
Nous rencontrons une subtilité : un identificateur x de variable peut désigner soit :
Une L-value est une expression qui peut se situer dans le membre gauche d’une affectation
(l’expression peut recevoir une valeur).
Une R-value est une expression qui peut se situer dans le membre droit d’une affectation (une
valeur peut être lue depuis l’expression).
Note 1 : c’est le contexte qui permet de dire si une expression est une L-value ou une R-value.
Note 2 : toute L-value peut-être une R-value (pour un contexte différent), mais pas l’inverse.
35 / 399
Adresse d’une variable
L’adresse d’une variable est une valeur entière spécifiant la position de la variable en mémoire.
36 / 399
Adresse d’une variable
L’adresse d’une variable est une valeur entière spécifiant la position de la variable en mémoire.
L’adresse d’une variable est attribuée à sa déclaration par le système à l’exécution. Elle ne peut
pas être choisie par le programmeur ni être modifiée.
36 / 399
Adresse d’une variable
L’adresse d’une variable est une valeur entière spécifiant la position de la variable en mémoire.
L’adresse d’une variable est attribuée à sa déclaration par le système à l’exécution. Elle ne peut
pas être choisie par le programmeur ni être modifiée.
On accède à l’adresse d’une variable par son identificateur précédé de l’opérateur & .
36 / 399
Adresse d’une variable
L’adresse d’une variable est une valeur entière spécifiant la position de la variable en mémoire.
L’adresse d’une variable est attribuée à sa déclaration par le système à l’exécution. Elle ne peut
pas être choisie par le programmeur ni être modifiée.
On accède à l’adresse d’une variable par son identificateur précédé de l’opérateur & .
36 / 399
Portée lexicale d’une variable et variables locales
La portée lexicale d’une variable désigne la zone du programme dans laquelle la variable peut
être utilisée.
37 / 399
Portée lexicale d’une variable et variables locales
La portée lexicale d’une variable désigne la zone du programme dans laquelle la variable peut
être utilisée.
37 / 399
Portée lexicale d’une variable et variables locales
La portée lexicale d’une variable désigne la zone du programme dans laquelle la variable peut
être utilisée.
Sa portée lexicale s’étend aux instructions qui sont situées après sa déclaration dans le plus petit
bloc d’instructions qui la contient.
37 / 399
Portée lexicale d’une variable et variables locales
La portée lexicale d’une variable désigne la zone du programme dans laquelle la variable peut
être utilisée.
Sa portée lexicale s’étend aux instructions qui sont situées après sa déclaration dans le plus petit
bloc d’instructions qui la contient.
37 / 399
Portée lexicale d’une variable et variables locales
La portée lexicale d’une variable désigne la zone du programme dans laquelle la variable peut
être utilisée.
Sa portée lexicale s’étend aux instructions qui sont situées après sa déclaration dans le plus petit
bloc d’instructions qui la contient.
37 / 399
Portée lexicale d’une variable et variables locales
38 / 399
Portée lexicale d’une variable et variables locales
38 / 399
Portée lexicale d’une variable et blocs d’instructions
39 / 399
Portée lexicale d’une variable et blocs d’instructions
39 / 399
Portée lexicale d’une variable et blocs d’instructions
40 / 399
Portée lexicale d’une variable et blocs d’instructions
40 / 399
Portée lexicale d’une variable et blocs d’instructions
int a; int a;
a = 10; a = 10;
{ {
int a; a = 20;
a = 20; printf("%d ", a);
printf("%d ", a); }
} printf("%d\n", a);
printf("%d\n", a);
41 / 399
Portée lexicale d’une variable et blocs d’instructions
int a; int a;
a = 10; a = 10;
{ {
int a; a = 20;
a = 20; printf("%d ", a);
printf("%d ", a); }
} printf("%d\n", a);
printf("%d\n", a);
Dans le cas de gauche (déjà vu), l’affectation a = 20 n’a d’effet que sur la variable a déclarée à
l’intérieur du bloc. Ceci affiche 20 10 .
41 / 399
Portée lexicale d’une variable et blocs d’instructions
int a; int a;
a = 10; a = 10;
{ {
int a; a = 20;
a = 20; printf("%d ", a);
printf("%d ", a); }
} printf("%d\n", a);
printf("%d\n", a);
Dans le cas de gauche (déjà vu), l’affectation a = 20 n’a d’effet que sur la variable a déclarée à
l’intérieur du bloc. Ceci affiche 20 10 .
En revanche, les instructions de droite affichent 20 20 car il n’y a pas de déclaration de a dans
le bloc. L’affectation a = 20 modifie la variable a déclarée en l. 1.
41 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
42 / 399
Fonctions
Une fonction est constituée
1. d’un identificateur (qui suit les mêmes contraintes que ceux des variables) ;
2. d’une signature (la liste de ses paramètres et de leurs types) ;
3. d’un type de retour (le type de la valeur renvoyée par la fonction) ;
4. d’instructions (qui forment le corps de la fonction).
43 / 399
Fonctions
Une fonction est constituée
1. d’un identificateur (qui suit les mêmes contraintes que ceux des variables) ;
2. d’une signature (la liste de ses paramètres et de leurs types) ;
3. d’un type de retour (le type de la valeur renvoyée par la fonction) ;
4. d’instructions (qui forment le corps de la fonction).
La ligne constituée du type de retour, de l’identificateur et de la signature d’une fonction est son
prototype.
43 / 399
Fonctions
Une fonction est constituée
1. d’un identificateur (qui suit les mêmes contraintes que ceux des variables) ;
2. d’une signature (la liste de ses paramètres et de leurs types) ;
3. d’un type de retour (le type de la valeur renvoyée par la fonction) ;
4. d’instructions (qui forment le corps de la fonction).
La ligne constituée du type de retour, de l’identificateur et de la signature d’une fonction est son
prototype.
P.ex.,
int produit(int a, int b, int c) {
return a * b * c;
}
est une fonction d’identificateur produit , de signature (int a, int b, int c) et de type de retour
int .
43 / 399
Définition vs déclaration
44 / 399
Définition vs déclaration
44 / 399
Définition vs déclaration
44 / 399
Définition vs déclaration
44 / 399
Paramètres vs arguments
Il faut faire attention à bien distinguer les notions de paramètre et d’argument qui sont deux
choses différentes.
45 / 399
Paramètres vs arguments
Il faut faire attention à bien distinguer les notions de paramètre et d’argument qui sont deux
choses différentes.
On considère la fonction
int produit(int a, int b, int c) {
return a * b * c;
}
45 / 399
Paramètres vs arguments
Il faut faire attention à bien distinguer les notions de paramètre et d’argument qui sont deux
choses différentes.
On considère la fonction
int produit(int a, int b, int c) {
return a * b * c;
}
45 / 399
Paramètres vs arguments
Il faut faire attention à bien distinguer les notions de paramètre et d’argument qui sont deux
choses différentes.
On considère la fonction
int produit(int a, int b, int c) {
return a * b * c;
}
Lors de l’appel
produit(15, num, -3);
45 / 399
Paramètres vs arguments
Il faut faire attention à bien distinguer les notions de paramètre et d’argument qui sont deux
choses différentes.
On considère la fonction
int produit(int a, int b, int c) {
return a * b * c;
}
Lors de l’appel
produit(15, num, -3);
45 / 399
Portée lexicale des paramètres
Les variables locales d’une fonction ont pour portée lexicale la fonction elle-même (déjà
mentionné).
46 / 399
Portée lexicale des paramètres
Les variables locales d’une fonction ont pour portée lexicale la fonction elle-même (déjà
mentionné).
Il en est de même pour ses paramètres : leur portée lexicale est la fonction elle-même. On peut
voir la déclaration des paramètres d’une fonction dans son en-tête comme une déclaration de
variable.
46 / 399
Portée lexicale des paramètres
Les variables locales d’une fonction ont pour portée lexicale la fonction elle-même (déjà
mentionné).
Il en est de même pour ses paramètres : leur portée lexicale est la fonction elle-même. On peut
voir la déclaration des paramètres d’une fonction dans son en-tête comme une déclaration de
variable.
46 / 399
Pile
Lors de l’appel d’une fonction, les valeurs de ses arguments sont recopiées dans une zone de la
mémoire appelée pile.
Conséquence très importante : toute modification des paramètres dans une fonction ne
modifie pas les valeurs des arguments avec lesquels elle a été appelée.
P.ex.,
f();
l. 10 l. 11
47 / 399
Pile
Les variables locales d’une fonction (c.-à-d. les variables déclarées dans le corps de la fonction) se
situent dans la pile.
De plus, la valeur renvoyée (si son type de retour n’est pas void ) se situe dans la pile.
48 / 399
Pile
Les variables locales d’une fonction (c.-à-d. les variables déclarées dans le corps de la fonction) se
situent dans la pile.
De plus, la valeur renvoyée (si son type de retour n’est pas void ) se situe dans la pile.
Après avoir appelé une fonction, c.-à-d. juste après avoir renvoyé la valeur de retour, la pile se
trouve dans le même état qu’avant l’appel.
48 / 399
Pile
Les variables locales d’une fonction (c.-à-d. les variables déclarées dans le corps de la fonction) se
situent dans la pile.
De plus, la valeur renvoyée (si son type de retour n’est pas void ) se situe dans la pile.
Après avoir appelé une fonction, c.-à-d. juste après avoir renvoyé la valeur de retour, la pile se
trouve dans le même état qu’avant l’appel.
Conséquence très importante : toute variable locale à une fonction est non seulement invisible
mais n’existe plus en mémoire hors de la fonction et après son appel.
48 / 399
Pile
P.ex.,
void fct_1(int a) { void fct_2() {
int b, c; int x;
b = 1; x = 16;
fct_2();
c = 2; fct_1(x);
a = b + c; printf("%d\n", x);
} }
Configurations de pile :
c (l. 2, val. ? ) c (l. 2, val. 2 )
x (l. 10, val. 16 ) x (l. 10, val. 16 ) x (l. 10, val. 16 ) x (l. 10, val. 16 ) x (l. 10, val. 16 ) x (l. 10, val. 16 )
∅ ∅
l. 15 l. 11 l. 2 l. 3 l. 3 l. 6 l. 13 l. 17
49 / 399
Pile et fonctions récursives
Soit la fonction et la suite d’instructions
int fibo(int a) { int x;
if (a <= 1) x = fibo(4);
return a;
return fibo(a - 1) + fibo(a - 2);
}
50 / 399
Pile et fonctions récursives
Soit la fonction et la suite d’instructions
int fibo(int a) { int x;
if (a <= 1) x = fibo(4);
return a;
return fibo(a - 1) + fibo(a - 2);
}
fibo(4)
fibo(3) fibo(2)
fibo(1) fibo(0) 1 1 0
1 0
50 / 399
Fonctions à effet de bord
Une fonction est à effet de bord s’il existe au moins un jeu d’arguments qui fait que l’évaluation
de l’appel à la fonction sur ce jeu d’arguments modifie la mémoire par rapport à son état d’avant
l’appel.
51 / 399
Fonctions à effet de bord
Une fonction est à effet de bord s’il existe au moins un jeu d’arguments qui fait que l’évaluation
de l’appel à la fonction sur ce jeu d’arguments modifie la mémoire par rapport à son état d’avant
l’appel.
int f(int a, int b) { Cette fonction n’est pas à effet de bord. Elle
return 21 * a + b;
} renvoie une valeur sans modifier la mémoire.
51 / 399
Fonctions à effet de bord
Une fonction est à effet de bord s’il existe au moins un jeu d’arguments qui fait que l’évaluation
de l’appel à la fonction sur ce jeu d’arguments modifie la mémoire par rapport à son état d’avant
l’appel.
int f(int a, int b) { Cette fonction n’est pas à effet de bord. Elle
return 21 * a + b;
} renvoie une valeur sans modifier la mémoire.
51 / 399
Fonctions à effet de bord
int g(char c) {
int b;
b = 5;
return b + c;
}
52 / 399
Fonctions à effet de bord
52 / 399
Fonctions à effet de bord
char *allouer(int n) {
char *res;
res = (char *)
malloc(sizeof(char) * n);
return res;
}
52 / 399
Fonctions à effet de bord
52 / 399
Fonctions à effet de bord
52 / 399
Fonctions à effet de bord
52 / 399
Plan
Bases
Généralités
Expressions et instructions
Constructions syntaxiques
Variables
Fonctions et pile
Commandes préprocesseur
53 / 399
Commandes préprocesseur
Une commande préprocesseur (ou directive préprocesseur) est une ligne qui commence par # .
54 / 399
Commandes préprocesseur
Une commande préprocesseur (ou directive préprocesseur) est une ligne qui commence par # .
Le préprocesseur est une unité qui intervient lors de la compilation. Son rôle est de traiter les
commandes préprocesseur.
Il fonctionne en construisant une nouvelle version du programme en remplaçant chaque
commande préprocesseur par des expressions en C adéquates.
54 / 399
Commandes préprocesseur
Une commande préprocesseur (ou directive préprocesseur) est une ligne qui commence par # .
Le préprocesseur est une unité qui intervient lors de la compilation. Son rôle est de traiter les
commandes préprocesseur.
Il fonctionne en construisant une nouvelle version du programme en remplaçant chaque
commande préprocesseur par des expressions en C adéquates.
54 / 399
Inclusions de fichiers
La commande préprocesseur
#include <NOM.h>
permet d’inclure le fichier NOM.h dans le programme pour bénéficier des fonctionnalités qu’il
apporte.
55 / 399
Inclusions de fichiers
La commande préprocesseur
#include <NOM.h>
permet d’inclure le fichier NOM.h dans le programme pour bénéficier des fonctionnalités qu’il
apporte.
Le préprocesseur résout cette commande en recopiant le contenu de NOM.h à l’endroit où elle est
invoquée.
55 / 399
Inclusions de fichiers
La commande préprocesseur
#include <NOM.h>
permet d’inclure le fichier NOM.h dans le programme pour bénéficier des fonctionnalités qu’il
apporte.
Le préprocesseur résout cette commande en recopiant le contenu de NOM.h à l’endroit où elle est
invoquée.
55 / 399
Inclusions de fichiers
La commande préprocesseur
#include <NOM.h>
permet d’inclure le fichier NOM.h dans le programme pour bénéficier des fonctionnalités qu’il
apporte.
Le préprocesseur résout cette commande en recopiant le contenu de NOM.h à l’endroit où elle est
invoquée.
55 / 399
Définitions de symboles
La commande préprocesseur
#define SYMB EXP
permet de définir un alias SYMB pour l’expression EXP . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB .
56 / 399
Définitions de symboles
La commande préprocesseur
#define SYMB EXP
permet de définir un alias SYMB pour l’expression EXP . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB .
56 / 399
Définitions de symboles
La commande préprocesseur
#define SYMB EXP
permet de définir un alias SYMB pour l’expression EXP . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB .
À gauche (resp. à droite), des instructions avant (resp. après) le passage du préprocesseur :
#define NB 5 ⁄* Rien. *⁄
#define CHAINE "cba\n" ⁄* Rien. *⁄
... ...
for (i = 1 ; i <= NB ; ++i) { for (i = 1 ; i <= 5 ; ++i) {
printf("%s", CHAINE); printf("%s", "cba\n");
} }
56 / 399
Définitions de symboles
La commande préprocesseur
#define SYMB EXP
permet de définir un alias SYMB pour l’expression EXP . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB .
À gauche (resp. à droite), des instructions avant (resp. après) le passage du préprocesseur :
#define NB 5 ⁄* Rien. *⁄
#define CHAINE "cba\n" ⁄* Rien. *⁄
... ...
for (i = 1 ; i <= NB ; ++i) { for (i = 1 ; i <= 5 ; ++i) {
printf("%s", CHAINE); printf("%s", "cba\n");
} }
Par convention, tout alias est constitué de lettres majuscules, de chiffres ou de tirets bas.
56 / 399
Macro-instructions à paramètres
La commande préprocesseur
#define SYMB(P1, P2, ..., Pn) EXP
permet de définir une macro-instruction à paramètres SYMB . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB paramétrable par des paramètres P1 , P2 ,
. . ., Pn .
57 / 399
Macro-instructions à paramètres
La commande préprocesseur
#define SYMB(P1, P2, ..., Pn) EXP
permet de définir une macro-instruction à paramètres SYMB . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB paramétrable par des paramètres P1 , P2 ,
. . ., Pn .
Le préprocesseur résout toute invocation SYMB(A1, A2, ..., An) en la remplaçant par l’expression
obtenue en substituant Ai à toute occurrence du paramètre Pi dans EXP .
57 / 399
Macro-instructions à paramètres
La commande préprocesseur
#define SYMB(P1, P2, ..., Pn) EXP
permet de définir une macro-instruction à paramètres SYMB . Ceci autorise à faire référence à
l’expression EXP par l’intermédiaire du symbole SYMB paramétrable par des paramètres P1 , P2 ,
. . ., Pn .
Le préprocesseur résout toute invocation SYMB(A1, A2, ..., An) en la remplaçant par l’expression
obtenue en substituant Ai à toute occurrence du paramètre Pi dans EXP .
À gauche (resp. à droite), des instructions avant (resp. après) le passage du préprocesseur :
57 / 399
Macro-instructions à paramètres
58 / 399
Macro-instructions à paramètres
58 / 399
Macro-instructions à paramètres
Solution : il faut placer des parenthèses autour des paramètres des macro-instructions à
paramètres :
#define CARRE(a) (a) * (a)
58 / 399
Macro-instructions à paramètres
Solution : il faut placer des parenthèses autour des paramètres des macro-instructions à
paramètres :
#define CARRE(a) (a) * (a)
58 / 399
Macro-instructions à paramètres
59 / 399
Macro-instructions à paramètres
59 / 399
Macro-instructions à paramètres
59 / 399
Macro-instructions à paramètres
De cette façon, 5 * DOUBLE(x) est remplacée par 5 * ((x) + (x)) comme désiré.
59 / 399
Macro-instructions à paramètres
Attention, l’usage de macro-instructions à paramètres peut provoquer des évaluations
multiples d’une même expression.
60 / 399
Macro-instructions à paramètres
Attention, l’usage de macro-instructions à paramètres peut provoquer des évaluations
multiples d’une même expression.
Considérons par exemple
#define MAX(a, b) ((a) > (b) ? (a) : (b))
60 / 399
Macro-instructions à paramètres
Attention, l’usage de macro-instructions à paramètres peut provoquer des évaluations
multiples d’une même expression.
Considérons par exemple
#define MAX(a, b) ((a) > (b) ? (a) : (b))
où res est une variable entière, f est une fonction qui renvoie un entier et t1 et t2 sont des
arguments acceptés par f .
60 / 399
Macro-instructions à paramètres
Attention, l’usage de macro-instructions à paramètres peut provoquer des évaluations
multiples d’une même expression.
Considérons par exemple
#define MAX(a, b) ((a) > (b) ? (a) : (b))
où res est une variable entière, f est une fonction qui renvoie un entier et t1 et t2 sont des
arguments acceptés par f .
Ceci est remplacé par le préprocesseur en
res = ((f(t1)) > (f(t2)) ? (f(t1)) : (f(t2)));
60 / 399
Macro-instructions à paramètres
Attention, l’usage de macro-instructions à paramètres peut provoquer des évaluations
multiples d’une même expression.
Considérons par exemple
#define MAX(a, b) ((a) > (b) ? (a) : (b))
où res est une variable entière, f est une fonction qui renvoie un entier et t1 et t2 sont des
arguments acceptés par f .
Ceci est remplacé par le préprocesseur en
res = ((f(t1)) > (f(t2)) ? (f(t1)) : (f(t2)));
61 / 399
Macro-instructions de contrôle de compilation
À gauche, le code ... n’est considéré que si l’alias SYMB est défini.
Au centre, le code ... n’est considéré que si l’alias SYMB n’est pas défini.
61 / 399
Macro-instructions de contrôle de compilation
#include <stdio.h>
#else
#define GAUCHE_DROITE
int rechercher(char *tab,
int main() {
int n, char x) {
#ifdef GAUCHE_DROITE int res;
int i;
char tab[] = "chaine de test";
for (i = n - 1 ; i >= 0 ; --i)
int rechercher(char *tab,
if (tab[i] == x)
int n, char x) { res = rechercher(tab, 14, ’t’);
return i;
int i; printf("%d\n", res);
return -1;
for (i = 0 ; i < n ; ++i) }
}
if (tab[i] == x)
return i;
#endif
return -1;
}
Ici, on donne deux algorithmes pour localiser la première occurrence d’une lettre dans un
tableau : de la gauche vers la droite, ou bien de la droite vers la gauche.
Ce programme affiche 10 ; si on renomme GAUCHE_DROITE (en l. 4), il affiche 13 .
62 / 399
Plan
Habitudes
Mise en page
Gestion d’erreurs
Assertions d’entrée
63 / 399
Plan
Habitudes
Mise en page
Gestion d’erreurs
Assertions d’entrée
64 / 399
Mise en page d’un programme
1. l’indentation ;
4. la documentation.
65 / 399
Indentation
L’indentation consiste à disposer des caractères blancs au début de certaines lignes d’un
programme.
Contrairement au Python, celle-ci ne modifie pas le comportement d’un programme.
L’objectif est d’augmenter sa lisibilité.
66 / 399
Indentation
L’indentation consiste à disposer des caractères blancs au début de certaines lignes d’un
programme.
Contrairement au Python, celle-ci ne modifie pas le comportement d’un programme.
L’objectif est d’augmenter sa lisibilité.
Règle : on place quatre espaces (pas de tabulation) avant chaque instruction d’un bloc et on
incrémente cet espacement de quatre en quatre en fonction de la profondeur des blocs.
66 / 399
Indentation
L’indentation consiste à disposer des caractères blancs au début de certaines lignes d’un
programme.
Contrairement au Python, celle-ci ne modifie pas le comportement d’un programme.
L’objectif est d’augmenter sa lisibilité.
Règle : on place quatre espaces (pas de tabulation) avant chaque instruction d’un bloc et on
incrémente cet espacement de quatre en quatre en fonction de la profondeur des blocs.
⁄* Correct. *⁄
a␣=␣8;
if␣(b␣>=␣0)␣{
␣␣␣␣printf("%d\n",␣a);
␣␣␣␣a␣=␣0;
␣␣␣␣for␣(i␣=␣0␣;␣i␣<=␣b␣;␣++i)
␣␣␣␣␣␣␣␣a␣/=␣2;
}
66 / 399
Indentation
L’indentation consiste à disposer des caractères blancs au début de certaines lignes d’un
programme.
Contrairement au Python, celle-ci ne modifie pas le comportement d’un programme.
L’objectif est d’augmenter sa lisibilité.
Règle : on place quatre espaces (pas de tabulation) avant chaque instruction d’un bloc et on
incrémente cet espacement de quatre en quatre en fonction de la profondeur des blocs.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣8; a␣=␣8;
if␣(b␣>=␣0)␣{ if␣(b␣>=␣0)␣{
␣␣␣␣printf("%d\n",␣a); ␣␣printf("%d\n",␣a);
␣␣␣␣a␣=␣0; ␣␣a␣=␣0;
␣␣␣␣for␣(i␣=␣0␣;␣i␣<=␣b␣;␣++i) ␣␣for␣(i␣=␣0␣;␣i␣<=␣b␣;␣++i)
␣␣␣␣␣␣␣␣a␣/=␣2; ␣␣␣␣␣a␣/=␣2;
} }
66 / 399
Organisation des blocs
Nous avons vu qu’un bloc des une suite d’instructions délimitée par des accolades. Il se trouve
attaché à une instruction de branchement ou de boucle. Il peut aussi être indépendant.
On revient à la ligne après une accolade ouvrante.
L’accolade fermante se trouve horizontalement au niveau du début de la ligne qui contient
l’accolade ouvrante.
67 / 399
Organisation des blocs
Nous avons vu qu’un bloc des une suite d’instructions délimitée par des accolades. Il se trouve
attaché à une instruction de branchement ou de boucle. Il peut aussi être indépendant.
On revient à la ligne après une accolade ouvrante.
L’accolade fermante se trouve horizontalement au niveau du début de la ligne qui contient
l’accolade ouvrante.
⁄* Correct. *⁄
if (valeur >= 1) {
valeur -= 1;
}
67 / 399
Organisation des blocs
Nous avons vu qu’un bloc des une suite d’instructions délimitée par des accolades. Il se trouve
attaché à une instruction de branchement ou de boucle. Il peut aussi être indépendant.
On revient à la ligne après une accolade ouvrante.
L’accolade fermante se trouve horizontalement au niveau du début de la ligne qui contient
l’accolade ouvrante.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
if (valeur >= 1) { if (valeur >= 1)
valeur -= 1; {
} valeur -= 1;
}
67 / 399
Organisation des blocs
Nous avons vu qu’un bloc des une suite d’instructions délimitée par des accolades. Il se trouve
attaché à une instruction de branchement ou de boucle. Il peut aussi être indépendant.
On revient à la ligne après une accolade ouvrante.
L’accolade fermante se trouve horizontalement au niveau du début de la ligne qui contient
l’accolade ouvrante.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
if (valeur >= 1) { if (valeur >= 1)
valeur -= 1; {
} valeur -= 1;
}
⁄* Correct. *⁄
valeur = 1;
{
int a;
valeur = 10;
}
67 / 399
Organisation des blocs
Nous avons vu qu’un bloc des une suite d’instructions délimitée par des accolades. Il se trouve
attaché à une instruction de branchement ou de boucle. Il peut aussi être indépendant.
On revient à la ligne après une accolade ouvrante.
L’accolade fermante se trouve horizontalement au niveau du début de la ligne qui contient
l’accolade ouvrante.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
if (valeur >= 1) { if (valeur >= 1)
valeur -= 1; {
} valeur -= 1;
}
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
valeur = 1; valeur = 1; {
{ int a;
int a; valeur = 10;
valeur = 10; }
}
67 / 399
Organisation des espaces
⁄* Correct. *⁄
a␣=␣b␣*␣2␣+␣5;
68 / 399
Organisation des espaces
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣b␣*␣2␣+␣5; a␣=␣b*2␣+␣5;
68 / 399
Organisation des espaces
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣b␣*␣2␣+␣5; a␣=␣b*2␣+␣5;
⁄* Correct. *⁄
f(a,␣b,␣c,␣16);
68 / 399
Organisation des espaces
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣b␣*␣2␣+␣5; a␣=␣b*2␣+␣5;
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
f(a,␣b,␣c,␣16); f(a,b,c,16);
68 / 399
Organisation des espaces
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣b␣*␣2␣+␣5; a␣=␣b*2␣+␣5;
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
f(a,␣b,␣c,␣16); f(a,b,c,16);
On ne place pas d’espace après une parenthèse ouvrante ou avant une parenthèse fermante.
⁄* Correct. *⁄
a␣=␣(f(a,␣3)␣+␣a)␣*␣2;
68 / 399
Organisation des espaces
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣b␣*␣2␣+␣5; a␣=␣b*2␣+␣5;
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
f(a,␣b,␣c,␣16); f(a,b,c,16);
On ne place pas d’espace après une parenthèse ouvrante ou avant une parenthèse fermante.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
a␣=␣(f(a,␣3)␣+␣a)␣*␣2; a␣=␣(f(␣a,␣3)␣+␣a␣)␣*␣2;
68 / 399
Choix des identificateurs
Les identificateurs doivent à la fois renseigner sur le rôle des entités auxquelles ils
appartiennent (variables, paramètres, fonctions, modules, etc.) et être concis.
69 / 399
Choix des identificateurs
Les identificateurs doivent à la fois renseigner sur le rôle des entités auxquelles ils
appartiennent (variables, paramètres, fonctions, modules, etc.) et être concis.
69 / 399
Choix des identificateurs
Les identificateurs doivent à la fois renseigner sur le rôle des entités auxquelles ils
appartiennent (variables, paramètres, fonctions, modules, etc.) et être concis.
69 / 399
Choix des identificateurs
Les identificateurs doivent à la fois renseigner sur le rôle des entités auxquelles ils
appartiennent (variables, paramètres, fonctions, modules, etc.) et être concis.
⁄* Identificateur acceptable. *⁄
nb_parties
69 / 399
Choix des identificateurs
Les identificateurs doivent à la fois renseigner sur le rôle des entités auxquelles ils
appartiennent (variables, paramètres, fonctions, modules, etc.) et être concis.
⁄* Identificateur acceptable. *⁄
nb_parties
69 / 399
Choix des identificateurs
Identificateurs de variables :
Les seuls identificateurs d’une lettre autorisés sont i , j , k , etc., pour les indices de boucles.
70 / 399
Choix des identificateurs
Identificateurs de variables :
Les seuls identificateurs d’une lettre autorisés sont i , j , k , etc., pour les indices de boucles.
Les majuscules sont interdites plusieurs mots, ces derniers sont ⁄* Correct. *⁄
nb_parties
dans les identificateurs. Dans un séparés par des sous-tirets.
identificateur composé de ⁄* Incorrect. *⁄
nbParties
70 / 399
Choix des identificateurs
Identificateurs de variables :
Les seuls identificateurs d’une lettre autorisés sont i , j , k , etc., pour les indices de boucles.
Les majuscules sont interdites plusieurs mots, ces derniers sont ⁄* Correct. *⁄
nb_parties
dans les identificateurs. Dans un séparés par des sous-tirets.
identificateur composé de ⁄* Incorrect. *⁄
nbParties
70 / 399
Choix des identificateurs
Identificateurs de variables :
Les seuls identificateurs d’une lettre autorisés sont i , j , k , etc., pour les indices de boucles.
Les majuscules sont interdites plusieurs mots, ces derniers sont ⁄* Correct. *⁄
nb_parties
dans les identificateurs. Dans un séparés par des sous-tirets.
identificateur composé de ⁄* Incorrect. *⁄
nbParties
70 / 399
Choix des identificateurs
Identificateurs de variables :
Les seuls identificateurs d’une lettre autorisés sont i , j , k , etc., pour les indices de boucles.
Les majuscules sont interdites plusieurs mots, ces derniers sont ⁄* Correct. *⁄
nb_parties
dans les identificateurs. Dans un séparés par des sous-tirets.
identificateur composé de ⁄* Incorrect. *⁄
nbParties
Identificateurs de types.
71 / 399
Documentation
Un programme est documenté par des commentaires. Ce sont des phrases, placées entre ⁄* et
*⁄ .
On documente chaque fichier de programme, au tout début, par
I les prénoms et noms des auteurs ;
I la date de création (au format jour-mois-année) ;
I la date de modification (au format précédent).
⁄* Auteur : A. M. Turing
* Creation : 23-06-1912
* Modification : 07-06-1954 *⁄
71 / 399
Documentation
Un programme est documenté par des commentaires. Ce sont des phrases, placées entre ⁄* et
*⁄ .
On documente chaque fichier de programme, au tout début, par
I les prénoms et noms des auteurs ;
I la date de création (au format jour-mois-année) ;
I la date de modification (au format précédent).
⁄* Auteur : A. M. Turing
* Creation : 23-06-1912
* Modification : 07-06-1954 *⁄
71 / 399
Documentation
Un programme est documenté par des commentaires. Ce sont des phrases, placées entre ⁄* et
*⁄ .
On documente chaque fichier de programme, au tout début, par
I les prénoms et noms des auteurs ;
I la date de création (au format jour-mois-année) ;
I la date de modification (au format précédent).
⁄* Auteur : A. M. Turing
* Creation : 23-06-1912
* Modification : 07-06-1954 *⁄
71 / 399
Documentation des fonctions
On documente la plupart des fonctions par des commentaires situés avant leur déclaration (ou
leur définition).
72 / 399
Documentation des fonctions
On documente la plupart des fonctions par des commentaires situés avant leur déclaration (ou
leur définition).
Un commentaire de fonction explique
I le rôle de chaque paramètre ;
I ce que renvoie la fonction ;
I l’effet produit par la fonction.
72 / 399
Documentation des fonctions
On documente la plupart des fonctions par des commentaires situés avant leur déclaration (ou
leur définition).
Un commentaire de fonction explique
I le rôle de chaque paramètre ;
I ce que renvoie la fonction ;
I l’effet produit par la fonction.
⁄* Correct. *⁄
⁄* Renvoie le plus grand entier
* parmi ‘a‘ et ‘b‘. *⁄
int max(int a, int b) {
if (a >= b)
return a;
return b;
}
72 / 399
Documentation des fonctions
On documente la plupart des fonctions par des commentaires situés avant leur déclaration (ou
leur définition).
Un commentaire de fonction explique
I le rôle de chaque paramètre ;
I ce que renvoie la fonction ;
I l’effet produit par la fonction.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
⁄* Renvoie le plus grand entier ⁄* Calcule le maximum de deux
* parmi ‘a‘ et ‘b‘. *⁄ * entiers. *⁄
int max(int a, int b) { int max(int a, int b) {
if (a >= b) if (a >= b)
return a; return a;
return b; return b;
} }
72 / 399
Plan
Habitudes
Mise en page
Gestion d’erreurs
Assertions d’entrée
73 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Détecter les erreurs
74 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
On écrira la plupart des fonctions selon le schéma suivant :
I le type de retour est int et la valeur de retour, le code d’erreur, renseigne si l’exécution de la
fonction s’est bien déroulée ;
I la (ou les) valeur(s) « renvoyée(s) » par la fonction se fait par un (des) passage(s) par adresse.
75 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
76 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
I une valeur négative ou nulle renseigne qu’une erreur s’est produite. En général, la fonction renvoie
0 dans ce cas. On peut être plus précis et exprimer une erreur particulière en renvoyant des autres
valeurs négatives.
76 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
I une valeur négative ou nulle renseigne qu’une erreur s’est produite. En général, la fonction renvoie
0 dans ce cas. On peut être plus précis et exprimer une erreur particulière en renvoyant des autres
valeurs négatives.
I une valeur strictement positive signifie que l’exécution de la fonction s’est bien déroulée. En
général, la fonction renvoie 1 dans ce cas. On peut être plus précis et exprimer une information
particulière en renvoyant des autres valeurs strictement positives.
76 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
I une valeur négative ou nulle renseigne qu’une erreur s’est produite. En général, la fonction renvoie
0 dans ce cas. On peut être plus précis et exprimer une erreur particulière en renvoyant des autres
valeurs négatives.
I une valeur strictement positive signifie que l’exécution de la fonction s’est bien déroulée. En
général, la fonction renvoie 1 dans ce cas. On peut être plus précis et exprimer une information
particulière en renvoyant des autres valeurs strictement positives.
Attention : il y a des fonctions de la librairie standard qui ne suivent pas cette spécification.
76 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
I une valeur négative ou nulle renseigne qu’une erreur s’est produite. En général, la fonction renvoie
0 dans ce cas. On peut être plus précis et exprimer une erreur particulière en renvoyant des autres
valeurs négatives.
I une valeur strictement positive signifie que l’exécution de la fonction s’est bien déroulée. En
général, la fonction renvoie 1 dans ce cas. On peut être plus précis et exprimer une information
particulière en renvoyant des autres valeurs strictement positives.
Attention : il y a des fonctions de la librairie standard qui ne suivent pas cette spécification.
De notre côté, nous allons la suivre à la lettre dans les fonctions que nous écrirons.
76 / 399
Mécanisme de gestion d’erreurs
Le code d’erreur d’une fonction ayant un prototype standard suit la spécification suivante :
I une valeur négative ou nulle renseigne qu’une erreur s’est produite. En général, la fonction renvoie
0 dans ce cas. On peut être plus précis et exprimer une erreur particulière en renvoyant des autres
valeurs négatives.
I une valeur strictement positive signifie que l’exécution de la fonction s’est bien déroulée. En
général, la fonction renvoie 1 dans ce cas. On peut être plus précis et exprimer une information
particulière en renvoyant des autres valeurs strictement positives.
Attention : il y a des fonctions de la librairie standard qui ne suivent pas cette spécification.
De notre côté, nous allons la suivre à la lettre dans les fonctions que nous écrirons.
Remarque : le fonctionnement des codes d’erreurs de chaque fonction écrite doit être spécifié
dans sa documentation.
76 / 399
Exemple 1 de gestion d’erreur
Considérons la fonction
int division(float x, float y, float *res) {
if (y == 0)
return 0;
*res = x / y;
return 1;
}
77 / 399
Exemple 1 de gestion d’erreur
Considérons la fonction
int division(float x, float y, float *res) {
if (y == 0)
return 0;
*res = x / y;
return 1;
}
Les entrées sont les flottants x et y . La sortie est res ; c’est une adresse qui pointera sur le
résultat de la division de x par y .
77 / 399
Exemple 1 de gestion d’erreur
Considérons la fonction
int division(float x, float y, float *res) {
if (y == 0)
return 0;
*res = x / y;
return 1;
}
Les entrées sont les flottants x et y . La sortie est res ; c’est une adresse qui pointera sur le
résultat de la division de x par y .
La valeur de retour est un code d’erreur : il vaut 0 lorsque la division ne peut pas être calculée
( y nul) et vaut 1 sinon.
77 / 399
Exemple 1 de gestion d’erreur
Considérons la fonction
int division(float x, float y, float *res) {
if (y == 0)
return 0;
*res = x / y;
return 1;
}
Les entrées sont les flottants x et y . La sortie est res ; c’est une adresse qui pointera sur le
résultat de la division de x par y .
La valeur de retour est un code d’erreur : il vaut 0 lorsque la division ne peut pas être calculée
( y nul) et vaut 1 sinon.
On remarque que l’on ne modifie pas *res lorsque le calcul ne peut pas être réalisé.
77 / 399
Exemple 2 de gestion d’erreur
Considérons la fonction
int nb_min_maj(char *chaine, int *res_min, int *res_maj) {
int i;
*res_min = 0;
*res_maj = 0;
i = 0;
while (chaine[i] != ’\0’) {
if ((’a’ <= chaine[i]) && (chaine[i] <= ’z’))
*res_min += 1;
else if ((’A’ <= chaine[i]) && (chaine[i] <= ’Z’))
*res_maj += 1;
else
return 0;
i += 1;
}
return i;
}
78 / 399
Exemple 2 de gestion d’erreur
Considérons la fonction
int nb_min_maj(char *chaine, int *res_min, int *res_maj) {
int i;
*res_min = 0;
*res_maj = 0;
i = 0;
while (chaine[i] != ’\0’) {
if ((’a’ <= chaine[i]) && (chaine[i] <= ’z’))
*res_min += 1;
else if ((’A’ <= chaine[i]) && (chaine[i] <= ’Z’))
*res_maj += 1;
else
return 0;
i += 1;
}
return i;
}
L’entrée est la chaîne de caractères chaine . Les sorties sont res_min et res_maj ; ces adresses pointeront
sur le nombre de minuscules et de majuscules dans chaine .
78 / 399
Exemple 2 de gestion d’erreur
Considérons la fonction
int nb_min_maj(char *chaine, int *res_min, int *res_maj) {
int i;
*res_min = 0;
*res_maj = 0;
i = 0;
while (chaine[i] != ’\0’) {
if ((’a’ <= chaine[i]) && (chaine[i] <= ’z’))
*res_min += 1;
else if ((’A’ <= chaine[i]) && (chaine[i] <= ’Z’))
*res_maj += 1;
else
return 0;
i += 1;
}
return i;
}
L’entrée est la chaîne de caractères chaine . Les sorties sont res_min et res_maj ; ces adresses pointeront
sur le nombre de minuscules et de majuscules dans chaine .
La valeur de retour est un code d’erreur : il vaut 0 si un caractère non alphabétique apparaît dans chaine
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
I scanf renvoie le nombre d’affectations réalisées lors de la lecture. La valeur EOF est renvoyée si une
erreur de lecture a lieu ;
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
I scanf renvoie le nombre d’affectations réalisées lors de la lecture. La valeur EOF est renvoyée si une
erreur de lecture a lieu ;
I malloc renvoie un pointeur vers la zone de la mémoire allouée. Lorsque l’allocation échoue, la valeur
NULL est renvoyée ;
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
I scanf renvoie le nombre d’affectations réalisées lors de la lecture. La valeur EOF est renvoyée si une
erreur de lecture a lieu ;
I malloc renvoie un pointeur vers la zone de la mémoire allouée. Lorsque l’allocation échoue, la valeur
NULL est renvoyée ;
I fopen renvoie un pointeur sur le fichier ouvert. Lorsque l’ouverture échoue, la valeur NULL est
renvoyée ;
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
I scanf renvoie le nombre d’affectations réalisées lors de la lecture. La valeur EOF est renvoyée si une
erreur de lecture a lieu ;
I malloc renvoie un pointeur vers la zone de la mémoire allouée. Lorsque l’allocation échoue, la valeur
NULL est renvoyée ;
I fopen renvoie un pointeur sur le fichier ouvert. Lorsque l’ouverture échoue, la valeur NULL est
renvoyée ;
I fclose renvoie 0 si la fermeture du fichier s’est bien déroulée (attention à ce cas particulier). Lorsque
la fermeture échoue, la valeur EOF est renvoyée.
79 / 399
Fonctions classiques à gestion d’erreurs
La librairie standard du C contient beaucoup de fonctions à gestion d’erreurs. Par exemple :
I printf renvoie le nombre de caractères écrits (sans compter ’\0’ );
I scanf renvoie le nombre d’affectations réalisées lors de la lecture. La valeur EOF est renvoyée si une
erreur de lecture a lieu ;
I malloc renvoie un pointeur vers la zone de la mémoire allouée. Lorsque l’allocation échoue, la valeur
NULL est renvoyée ;
I fopen renvoie un pointeur sur le fichier ouvert. Lorsque l’ouverture échoue, la valeur NULL est
renvoyée ;
I fclose renvoie 0 si la fermeture du fichier s’est bien déroulée (attention à ce cas particulier). Lorsque
la fermeture échoue, la valeur EOF est renvoyée.
Remarque : certaines de ces fonctions ont une gestion d’erreurs encore plus sophistiquée et
modifient des variables globales comme errno (de l’en-tête errno.h ) pour renseigner précisément
sur l’erreur survenue.
79 / 399
Emploi des fonctions à gestion d’erreurs
Les fonctions à gestion d’erreur renvoient des entiers. De ce fait, leur valeur de retour est une
expression booléenne.
80 / 399
Emploi des fonctions à gestion d’erreurs
Les fonctions à gestion d’erreur renvoient des entiers. De ce fait, leur valeur de retour est une
expression booléenne.
Nous pouvons donc combiner l’appel d’une fonction à gestion d’erreurs avec un test pour traiter
l’erreur éventuelle.
80 / 399
Emploi des fonctions à gestion d’erreurs
Les fonctions à gestion d’erreur renvoient des entiers. De ce fait, leur valeur de retour est une
expression booléenne.
Nous pouvons donc combiner l’appel d’une fonction à gestion d’erreurs avec un test pour traiter
l’erreur éventuelle.
Voici quelques exemples avec les deux fonctions précédentes :
if (division(8, a, &b) == 0) {
⁄* Traitement de l’erreur lors
* de la division par zero. *⁄
}
⁄* Instructions suivantes. *⁄
80 / 399
Emploi des fonctions à gestion d’erreurs
Les fonctions à gestion d’erreur renvoient des entiers. De ce fait, leur valeur de retour est une
expression booléenne.
Nous pouvons donc combiner l’appel d’une fonction à gestion d’erreurs avec un test pour traiter
l’erreur éventuelle.
Voici quelques exemples avec les deux fonctions précédentes :
if (division(8, a, &b) == 0) {
⁄* Traitement de l’erreur lors
* de la division par zero. *⁄
}
⁄* Instructions suivantes. *⁄
80 / 399
Interruption de l’exécution
Dans certains cas où une erreur survient, celle-ci peut être irrécupérable. Il faut donc interrompre
l’exécution du programme. On utilise pour cela la fonction
void exit(int status);
81 / 399
Interruption de l’exécution
Dans certains cas où une erreur survient, celle-ci peut être irrécupérable. Il faut donc interrompre
l’exécution du programme. On utilise pour cela la fonction
void exit(int status);
P.ex.,
⁄* Allocation dynamique. *⁄
tab = (int *) malloc(sizeof(int) * 1024);
81 / 399
Interruption de l’exécution
Dans certains cas où une erreur survient, celle-ci peut être irrécupérable. Il faut donc interrompre
l’exécution du programme. On utilise pour cela la fonction
void exit(int status);
P.ex.,
⁄* Allocation dynamique. *⁄
tab = (int *) malloc(sizeof(int) * 1024);
Habitudes
Mise en page
Gestion d’erreurs
Assertions d’entrée
82 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
83 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
Au lieu de gérer ces cas de figure par l’usage de codes d’erreur, il est possible de tester l’état des
arguments.
83 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
Au lieu de gérer ces cas de figure par l’usage de codes d’erreur, il est possible de tester l’état des
arguments.
Une pré-assertion (ou assertion d’entrée) est un test réalisé dans une fonction pour vérifier si elle
est appelée avec des arguments adéquats.
83 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
Au lieu de gérer ces cas de figure par l’usage de codes d’erreur, il est possible de tester l’état des
arguments.
Une pré-assertion (ou assertion d’entrée) est un test réalisé dans une fonction pour vérifier si elle
est appelée avec des arguments adéquats.
On utilise la fonction
void assert(int a);
83 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
Au lieu de gérer ces cas de figure par l’usage de codes d’erreur, il est possible de tester l’état des
arguments.
Une pré-assertion (ou assertion d’entrée) est un test réalisé dans une fonction pour vérifier si elle
est appelée avec des arguments adéquats.
On utilise la fonction
void assert(int a);
83 / 399
Assertions d’entrée
Lors d’un appel à une fonction, certains arguments peuvent être dans un état incohérent.
Au lieu de gérer ces cas de figure par l’usage de codes d’erreur, il est possible de tester l’état des
arguments.
Une pré-assertion (ou assertion d’entrée) est un test réalisé dans une fonction pour vérifier si elle
est appelée avec des arguments adéquats.
On utilise la fonction
void assert(int a);
83 / 399
Exemple 1 de fonction avec pré-assertions
Considérons la fonction
void afficher_tab(int tab[], int nb) {
int i;
assert(tab != NULL);
assert(nb >= 0);
for (i = 0 ; i < nb ; ++i)
printf("%d\n", tab[i]);
}
84 / 399
Exemple 1 de fonction avec pré-assertions
Considérons la fonction
void afficher_tab(int tab[], int nb) {
int i;
assert(tab != NULL);
assert(nb >= 0);
for (i = 0 ; i < nb ; ++i)
printf("%d\n", tab[i]);
}
84 / 399
Exemple 1 de fonction avec pré-assertions
Considérons la fonction
void afficher_tab(int tab[], int nb) {
int i;
assert(tab != NULL);
assert(nb >= 0);
for (i = 0 ; i < nb ; ++i)
printf("%d\n", tab[i]);
}
84 / 399
Conception de pré-assertions
Il est important de munir ses fonctions de pré-assertions les plus précises et complètes possibles.
Quelques règles :
85 / 399
Conception de pré-assertions
Il est important de munir ses fonctions de pré-assertions les plus précises et complètes possibles.
Quelques règles :
I la condition testée ne doit dépendre que des arguments d’une fonction (elle ne dépend pas
de données apprises à l’exécution) ;
85 / 399
Conception de pré-assertions
Il est important de munir ses fonctions de pré-assertions les plus précises et complètes possibles.
Quelques règles :
I la condition testée ne doit dépendre que des arguments d’une fonction (elle ne dépend pas
de données apprises à l’exécution) ;
I la condition testée doit être la plus atomique possible.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
assert(nb >= 0); assert((0<=nb) && (nb<=1024));
assert(nb <= 1024);
85 / 399
Conception de pré-assertions
Il est important de munir ses fonctions de pré-assertions les plus précises et complètes possibles.
Quelques règles :
I la condition testée ne doit dépendre que des arguments d’une fonction (elle ne dépend pas
de données apprises à l’exécution) ;
I la condition testée doit être la plus atomique possible.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
assert(nb >= 0); assert((0<=nb) && (nb<=1024));
assert(nb <= 1024);
I Pour les concevoir, il faut imaginer les pires cas possibles à capturer qui peuvent survenir
(p.ex., pointeurs nuls, quantités négatives, chaînes de caractères vides, etc.).
85 / 399
Conception de pré-assertions
Il est important de munir ses fonctions de pré-assertions les plus précises et complètes possibles.
Quelques règles :
I la condition testée ne doit dépendre que des arguments d’une fonction (elle ne dépend pas
de données apprises à l’exécution) ;
I la condition testée doit être la plus atomique possible.
⁄* Correct. *⁄ ⁄* Incorrect. *⁄
assert(nb >= 0); assert((0<=nb) && (nb<=1024));
assert(nb <= 1024);
I Pour les concevoir, il faut imaginer les pires cas possibles à capturer qui peuvent survenir
(p.ex., pointeurs nuls, quantités négatives, chaînes de caractères vides, etc.).
I Elles sont situées juste après les déclarations de variables dans le corps d’une fonction.
85 / 399
Redondance nécessaire des pré-assertions
Considérons les fonctions
86 / 399
Redondance nécessaire des pré-assertions
Considérons les fonctions
Raisonnement : il n’y a pas de pré-assertion dans somme_div mais cela n’est pas grave car les cas
problématiques sont capturés par div qui contient une pré-assertion.
86 / 399
Redondance nécessaire des pré-assertions
Considérons les fonctions
Raisonnement : il n’y a pas de pré-assertion dans somme_div mais cela n’est pas grave car les cas
problématiques sont capturés par div qui contient une pré-assertion.
Ceci est une fausse bonne idée : chaque fonction doit faire ses propres pré-assertions. Toute
erreur doit être capturée le plus en amont possible.
86 / 399
Redondance nécessaire des pré-assertions
Considérons les fonctions
Raisonnement : il n’y a pas de pré-assertion dans somme_div mais cela n’est pas grave car les cas
problématiques sont capturés par div qui contient une pré-assertion.
Ceci est une fausse bonne idée : chaque fonction doit faire ses propres pré-assertions. Toute
erreur doit être capturée le plus en amont possible.
86 / 399
Exemple 2 de fonction avec pré-assertion
assert(chaine != NULL);
assert(res_min != NULL);
assert(res_maj != NULL);
⁄* Suite inchangee. *⁄
}
87 / 399
Exemple 2 de fonction avec pré-assertion
assert(chaine != NULL);
assert(res_min != NULL);
assert(res_maj != NULL);
⁄* Suite inchangee. *⁄
}
On observe que le mécanisme de gestion d’erreurs par valeur de retour teste des
comportements incohérents complexes qui se déroulent à l’exécution, tandis que le mécanisme
de pré-assertion permet de capturer des erreurs évidentes lisibles directement sur les arguments.
87 / 399
Les pré-assertions pour corriger un programme
Considérons le programme
#include <stdio.h>
#include <assert.h>
return a / b;
}
int main() {
int a;
a = div(17, 0);
printf("%d\n", a);
return 0;
}
88 / 399
Les pré-assertions pour corriger un programme
Considérons le programme
return a / b;
}
int main() {
int a;
a = div(17, 0);
printf("%d\n", a);
return 0;
}
88 / 399
Les pré-assertions pour corriger un programme
Considérons le programme
a = div(17, 0);
printf("%d\n", a); On récolte la précieuse information sur le numéro
return 0;
de ligne de la pré-assertion non satisfaite qui
} produit l’arrêt précipité de l’exécution.
88 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
89 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
90 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
91 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
Un module est un ensemble de données et d’instructions qui permettent de gérer une partie bien
ciblée d’un projet.
91 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
Un module est un ensemble de données et d’instructions qui permettent de gérer une partie bien
ciblée d’un projet.
91 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
Un module est un ensemble de données et d’instructions qui permettent de gérer une partie bien
ciblée d’un projet.
91 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
Un module est un ensemble de données et d’instructions qui permettent de gérer une partie bien
ciblée d’un projet.
91 / 399
Modules
Modulariser un projet signifie le découper de manière cohérente en plusieurs parties plus petites.
Un module est un ensemble de données et d’instructions qui permettent de gérer une partie bien
ciblée d’un projet.
Il reste à savoir comment découper un projet de manière cohérente et comment utiliser les
outils offerts par le langage pour gérer ce découpage.
91 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
3. Il devient possible de réutiliser dans un nouveau projet un module créé dans un projet
antérieur.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
3. Il devient possible de réutiliser dans un nouveau projet un module créé dans un projet
antérieur.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
3. Il devient possible de réutiliser dans un nouveau projet un module créé dans un projet
antérieur.
92 / 399
Avantages offerts par la modularité
La modularité, illustration du principe stratégique universel
« diviser pour régner »,
offre les avantages suivants.
3. Il devient possible de réutiliser dans un nouveau projet un module créé dans un projet
antérieur.
92 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
93 / 399
Spécification d’un projet
94 / 399
Spécification d’un projet
94 / 399
Spécification d’un projet
94 / 399
Spécification d’un projet
94 / 399
Découpage du projet
Il y a deux parties bien distinctes dans ce projet :
1. la représentation des formules et le test de validité / contradiction ;
2. la gestion syntaxique des formules (lecture / écriture d’une formule dans un fichier).
95 / 399
Découpage du projet
Il y a deux parties bien distinctes dans ce projet :
1. la représentation des formules et le test de validité / contradiction ;
2. la gestion syntaxique des formules (lecture / écriture d’une formule dans un fichier).
Ces deux parties dictent le découpage suivant :
– test syntaxique
– type formule
d’une formule
– test validité
– chaîne vers formule
– test contradiction
– formule vers chaîne
Formule Parseur
– lecture fichier de
formules et écriture
des résultats
Main
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
96 / 399
Composition d’un module
Un module est composé de deux fichiers :
1. un fichier d’en-tête d’extension .h ;
2. un fichier source d’extension .c .
97 / 399
Composition d’un module
Un module est composé de deux fichiers :
1. un fichier d’en-tête d’extension .h ;
2. un fichier source d’extension .c .
Les noms de ces deux fichiers sont les mêmes (extension mise à part).
– ... – ...
– ... – ...
A.h A.c
97 / 399
Composition d’un module
Un module est composé de deux fichiers :
1. un fichier d’en-tête d’extension .h ;
2. un fichier source d’extension .c .
Les noms de ces deux fichiers sont les mêmes (extension mise à part).
– ... – ...
– ... – ...
A.h A.c
Seul le module principal est constitué d’un seul fichier source Main.c .
– ...
– ...
Main.c
97 / 399
Composition d’un module
Un module est composé de deux fichiers :
1. un fichier d’en-tête d’extension .h ;
2. un fichier source d’extension .c .
Les noms de ces deux fichiers sont les mêmes (extension mise à part).
– ... – ...
– ... – ...
A.h A.c
Seul le module principal est constitué d’un seul fichier source Main.c .
– ...
– ...
Main.c
97 / 399
Fichiers d’en-tête
98 / 399
Fichiers d’en-tête
Les prototypes qui y figurent sont ceux des fonctions que l’on souhaite rendre visibles
(utilisables) par d’autres modules.
98 / 399
Fichiers d’en-tête
Les prototypes qui y figurent sont ceux des fonctions que l’on souhaite rendre visibles
(utilisables) par d’autres modules.
⁄* Formule.h *⁄
typedef struct {
...
} Form;
98 / 399
Fichiers d’en-tête
Les fichiers d’en-tête sont ceux que le programmeur regarde en premier pour connaître le rôle
d’un type ou d’une fonction.
99 / 399
Fichiers d’en-tête
Les fichiers d’en-tête sont ceux que le programmeur regarde en premier pour connaître le rôle
d’un type ou d’une fonction.
De ce fait, c’est dans les fichiers d’en-tête que l’on commente chaque type et fonction pour
préciser leur rôle.
99 / 399
Fichiers d’en-tête
Les fichiers d’en-tête sont ceux que le programmeur regarde en premier pour connaître le rôle
d’un type ou d’une fonction.
De ce fait, c’est dans les fichiers d’en-tête que l’on commente chaque type et fonction pour
préciser leur rôle.
Par exemple, l’en-tête du module Formule devrait être de la forme
⁄* Formule.h *⁄
99 / 399
Fichiers sources
100 / 399
Fichiers sources
100 / 399
Fichiers sources
100 / 399
Fichiers sources
Il faut impérativement que toutes les fonctions déclarées dans le fichier d’en-tête du module
soient définies dans le fichier source correspondant.
101 / 399
Fichiers sources
Il faut impérativement que toutes les fonctions déclarées dans le fichier d’en-tête du module
soient définies dans le fichier source correspondant.
Il est en revanche possible de définir dans un fichier source des fonctions qui ne sont pas
déclarées dans l’en-tête correspondant.
101 / 399
Fichiers sources
Il faut impérativement que toutes les fonctions déclarées dans le fichier d’en-tête du module
soient définies dans le fichier source correspondant.
Il est en revanche possible de définir dans un fichier source des fonctions qui ne sont pas
déclarées dans l’en-tête correspondant.
Ainsi,
I une fonction définie dans un fichier source mais pas dans l’en-tête correspondant s’appelle
fonction privée ;
101 / 399
Fichiers sources
Il faut impérativement que toutes les fonctions déclarées dans le fichier d’en-tête du module
soient définies dans le fichier source correspondant.
Il est en revanche possible de définir dans un fichier source des fonctions qui ne sont pas
déclarées dans l’en-tête correspondant.
Ainsi,
I une fonction définie dans un fichier source mais pas dans l’en-tête correspondant s’appelle
fonction privée ;
I l’intérêt des fonctions privées est d’être des fonctions outils dont le champ d’application
est local au module dans lequel elles sont définies.
On ne souhaite pas les rendre utilisables en dehors.
101 / 399
Fichiers sources et fonctions privées
Une fonction privée se définit avec le mot clé static (à ne pas confondre avec le static pour la
déclaration de variables).
102 / 399
Fichiers sources et fonctions privées
Une fonction privée se définit avec le mot clé static (à ne pas confondre avec le static pour la
déclaration de variables).
Par exemple, on peut avoir besoin d’une fonction privée appartenant au module Formule qui
permet de compter le nombre d’occurrences d’un atome dans une formule.
102 / 399
Fichiers sources et fonctions privées
Une fonction privée se définit avec le mot clé static (à ne pas confondre avec le static pour la
déclaration de variables).
Par exemple, on peut avoir besoin d’une fonction privée appartenant au module Formule qui
permet de compter le nombre d’occurrences d’un atome dans une formule.
On la définit alors dans Formule.c par
static int nb_occ(Form *f, char atome) {
...
assert(f != NULL);
assert((’a’ <= atome) && (atome <= ’z’));
...
}
102 / 399
Fichiers sources et fonctions privées
Une fonction privée se définit avec le mot clé static (à ne pas confondre avec le static pour la
déclaration de variables).
Par exemple, on peut avoir besoin d’une fonction privée appartenant au module Formule qui
permet de compter le nombre d’occurrences d’un atome dans une formule.
On la définit alors dans Formule.c par
static int nb_occ(Form *f, char atome) {
...
assert(f != NULL);
assert((’a’ <= atome) && (atome <= ’z’));
...
}
La portée lexicale de cette fonction s’étend à tout ce qui suit sa définition dans le fichier
Formule.c . Elle est invisible ailleurs.
102 / 399
Fichiers sources et fonctions privées
C’est un contresens que de définir une fonction dans un fichier source sans le mot clé static et
sans l’avoir déclarée dans le fichier d’en-tête.
103 / 399
Fichiers sources et fonctions privées
C’est un contresens que de définir une fonction dans un fichier source sans le mot clé static et
sans l’avoir déclarée dans le fichier d’en-tête.
C’est aussi un contresens que de définir dans un fichier source une fonction déclarée dans le
fichier d’en-tête avec static .
103 / 399
Fichiers sources et fonctions privées
C’est un contresens que de définir une fonction dans un fichier source sans le mot clé static et
sans l’avoir déclarée dans le fichier d’en-tête.
C’est aussi un contresens que de définir dans un fichier source une fonction déclarée dans le
fichier d’en-tête avec static .
Ainsi, pour résumer, toute fonction définie dans un fichier source est
1. soit déclarée dans le fichier d’en-tête ;
103 / 399
Fichiers sources et fonctions privées
C’est un contresens que de définir une fonction dans un fichier source sans le mot clé static et
sans l’avoir déclarée dans le fichier d’en-tête.
C’est aussi un contresens que de définir dans un fichier source une fonction déclarée dans le
fichier d’en-tête avec static .
Ainsi, pour résumer, toute fonction définie dans un fichier source est
1. soit déclarée dans le fichier d’en-tête ;
2. soit non déclarée dans le fichier d’en-tête mais définie par static .
103 / 399
Allure d’un projet
104 / 399
Allure d’un projet
1. la 1re consiste à regrouper l’ensemble des fichiers d’en-tête et des fichiers sources dans un
même répertoire. Il y figure donc un nombre impair de fichiers (le module principal et les
paires en-tête / source pour chaque module) ;
104 / 399
Allure d’un projet
1. la 1re consiste à regrouper l’ensemble des fichiers d’en-tête et des fichiers sources dans un
même répertoire. Il y figure donc un nombre impair de fichiers (le module principal et les
paires en-tête / source pour chaque module) ;
2. la 2e consiste à séparer les fichiers du projet en deux répertoires frères, include et src , le
premier contenant les fichiers d’en-tête et l’autre, les fichiers sources et le module principal
du projet.
104 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
105 / 399
Inclusion de modules
106 / 399
Inclusion de modules
#include "Module.h"
106 / 399
Inclusion de modules
#include "Module.h"
106 / 399
Inclusion de modules
#include "Module.h"
106 / 399
Inclusion de modules
#include "Module.h"
106 / 399
Inclusion de modules
Par exemple, si A est un module définissant une fonction f , pour utiliser f dans un fichier B.c
⁄* B.c *⁄
#include "A.h"
...
107 / 399
Inclusion de modules
Par exemple, si A est un module définissant une fonction f , pour utiliser f dans un fichier B.c
⁄* B.c *⁄
#include "A.h"
...
⁄* Fichier.c ou Fichier.h *⁄
#include "A.h"
#include "B.h"
#include "C.h"
...
107 / 399
Inclusion de modules
Par exemple, si A est un module définissant une fonction f , pour utiliser f dans un fichier B.c
⁄* B.c *⁄
#include "A.h"
...
⁄* Fichier.c ou Fichier.h *⁄ de tout ce qui est déclaré et défini dans les modules
A , B et C .
#include "A.h"
#include "B.h"
#include "C.h"
... Attention : les modules inclus ne doivent pas
déclarer / définir des éléments d’un identificateur
De cette manière, Fichier.c ou Fichier.h bénéficie commun.
107 / 399
Inclusion de modules
Le fichier incluant n’a accès qu’au fichier d’en-tête du module, et donc qu’aux déclarations
effectuées.
108 / 399
Inclusion de modules
Le fichier incluant n’a accès qu’au fichier d’en-tête du module, et donc qu’aux déclarations
effectuées.
Le fichier incluant n’a pas besoin de connaître l’implantation du module.
108 / 399
Inclusion de modules
Le fichier incluant n’a accès qu’au fichier d’en-tête du module, et donc qu’aux déclarations
effectuées.
Le fichier incluant n’a pas besoin de connaître l’implantation du module.
⁄* Couple.h *⁄ ⁄* Couple.c *⁄
...
typedef int Couple[2]; int est_zero(Couple c) {
return (c[0] == 0) && (c[1]== 0);
int est_zero(Couple c); }
108 / 399
Inclusion de modules
Supposons que l’on ait besoin d’inclure le module Couple dans un fichier Fichier.c .
109 / 399
Inclusion de modules
Supposons que l’on ait besoin d’inclure le module Couple dans un fichier Fichier.c .
#include "Couple.h"
...
if (!est_zero(c))
afficher(c);
...
109 / 399
Inclusion de modules
Supposons que l’on ait besoin d’inclure le module Couple dans un fichier Fichier.c .
109 / 399
Inclusion de modules
Supposons que l’on ait besoin d’inclure le module Couple dans un fichier Fichier.c .
Fichier.c a besoin uniquement de connaître les types de retour et les signatures des fonctions
qu’il invoque (connus à leur déclaration). Il n’a à ce stade pas besoin de connaître les définitions
de ces fonctions.
109 / 399
Création complète d’un module
Pour créer un module A , il faut inclure son fichier d’en-tête A.h dans son fichier source A.c .
⁄* A.h *⁄ ⁄* A.c *⁄
110 / 399
Création complète d’un module
Pour créer un module A , il faut inclure son fichier d’en-tête A.h dans son fichier source A.c .
⁄* A.h *⁄ ⁄* A.c *⁄
De cette manière,
I d’une part, A.c a accès aux types et aux prototypes de fonctions déclarés dans A.h ;
110 / 399
Création complète d’un module
Pour créer un module A , il faut inclure son fichier d’en-tête A.h dans son fichier source A.c .
⁄* A.h *⁄ ⁄* A.c *⁄
De cette manière,
I d’une part, A.c a accès aux types et aux prototypes de fonctions déclarés dans A.h ;
I d’autre part, cela permet d’implanter les fonctions déclarées dans A.h sans contrainte
d’ordre.
110 / 399
Création complète d’un module
111 / 399
Création complète d’un module
111 / 399
Création complète d’un module
Problème : le contenu de C.h est copié deux fois dans D.c . Ceci n’est pas accepté par le
compilateur car il y a multiple déclaration d’un même symbole ( f ici).
111 / 399
Création complète d’un module
La parade consiste à inclure un fichier d’en-tête de manière conditionnelle : on procède à
l’inclusion que s’il n’a pas déjà été inclus.
On utilise pour cela les macro-instructions de contrôle de compilation #ifndef et #endif ainsi
que #define .
Le schéma général est
Ainsi, lors d’une inclusion de A.h , le pré-
/* A.h */
processeur vérifie si la macro __A__n’existe
#ifndef __A__
#define __A__
pas.
I Si elle n’existe pas, alors on la définit
/* Declaration de types */
... ( #define __A__ ) et le contenu du
/* Declaration de fonctions */
module est pris en compte ;
... I sinon, cela signifie que le contenu a
#endif déjà été pris en compte. Celui-ci n’est
pas repris en compte une 2e fois.
112 / 399
Squelette d’un module
#ifndef __A__
/* A.c */
#define __A__
#include "A.h"
/* Inclusions eventuelles de modules */
...
/* Inclusions eventuelles de modules */
...
/* Definitions eventuelles de macros */
...
/* Definitions eventuelles de fonctions privees */
...
/* Declarations eventuelles de types */
...
/* Definitions de toutes les fonctions declarees
* dans le fichier d’en-tete */
/* Declarations eventuelles de fonctions */
...
...
#endif
113 / 399
Exemple du module Parseur
/* Parseur.h */
/* Parseur.c */
#ifndef __PARSEUR__
#include "Parseur.h"
#define __PARSEUR__
#include <stdio.h>
#include "Formule.h"
#include <stdlib.h>
#include <assert.h>
/* Convertit la chaine de caracteres ‘ch‘ sensee
* representer une formule en une variable de type
int chaine_vers_form(Form *f, char *ch) {
* ‘Form‘, qui va etre ecrite dans ‘f‘. Renvoie ‘1‘
...
* si ‘ch‘ represente bien une formule et ‘0‘ sinon. */
assert(f != NULL);
int chaine_vers_form(Form *f, char *ch);
...
}
#endif
114 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
115 / 399
Graphes d’inclusions
On rappelle qu’un module A dépend d’un module B si le fichier d’en-tête de A inclut B .
116 / 399
Graphes d’inclusions
On rappelle qu’un module A dépend d’un module B si le fichier d’en-tête de A inclut B .
Pour visualiser l’allure d’un projet, on trace son graphe d’inclusions. On représente pour cela
chacun des modules qui le composent dans des cercles (sommets) et on trace des flèches (arcs)
de A vers B pour tout module A dépendant de B .
116 / 399
Graphes d’inclusions
On rappelle qu’un module A dépend d’un module B si le fichier d’en-tête de A inclut B .
Pour visualiser l’allure d’un projet, on trace son graphe d’inclusions. On représente pour cela
chacun des modules qui le composent dans des cercles (sommets) et on trace des flèches (arcs)
de A vers B pour tout module A dépendant de B .
Par exemple, le graphe d’inclusions
signifie que
A B
I A.h inclut B.h et D.h ;
I B.h inclut D.h ;
C D I C.h inclut D.h ;
I D.h n’inclut rien.
116 / 399
Graphes d’inclusions
On rappelle qu’un module A dépend d’un module B si le fichier d’en-tête de A inclut B .
Pour visualiser l’allure d’un projet, on trace son graphe d’inclusions. On représente pour cela
chacun des modules qui le composent dans des cercles (sommets) et on trace des flèches (arcs)
de A vers B pour tout module A dépendant de B .
Par exemple, le graphe d’inclusions
signifie que
A B
I A.h inclut B.h et D.h ;
I B.h inclut D.h ;
C D I C.h inclut D.h ;
I D.h n’inclut rien.
On ne mentionne pas dans les graphes d’inclusions les inclusions aux fichiers d’en-tête standards
( stdio.h , stdlib.h , assert.h , etc.).
116 / 399
Graphe d’inclusions et fichier principal
Le graphe d’inclusions d’un projet consistant à faire jouer l’ordinateur aux échecs contre un
humain peut être le suivant :
Piece IA
Position Main
Case IGraph
Tout projet contient un fichier source Main.c , le fichier principal du projet, où figure la fonction
main (le point d’entrée de l’exécution du programme). Celui-ci apparaît dans le graphe
d’inclusions.
117 / 399
Inclusions circulaires
Les graphes d’inclusions permettent d’avoir une vision globale de l’architecture d’un projet.
118 / 399
Inclusions circulaires
Les graphes d’inclusions permettent d’avoir une vision globale de l’architecture d’un projet.
Ils permettent aussi de mettre en évidence des problèmes de conception et notamment les
problèmes d’inclusion circulaire. Ce type de problème s’observe par la présence d’un cycle dans
le graphe d’inclusions :
A B
C D
118 / 399
Inclusions circulaires
Les graphes d’inclusions permettent d’avoir une vision globale de l’architecture d’un projet.
Ils permettent aussi de mettre en évidence des problèmes de conception et notamment les
problèmes d’inclusion circulaire. Ce type de problème s’observe par la présence d’un cycle dans
le graphe d’inclusions :
A B
C D
Règle importante : il ne doit jamais y avoir de cycle dans le graphe d’inclusions d’un projet. S’il
y a un cycle, c’est que le projet est mal découpé en modules.
118 / 399
Limiter les inclusions
La plupart des inclusions circulaires peuvent être évitées en réduisant au maximum les inclusions
de modules dans les fichiers d’en-tête en les faisant plutôt si possible dans les fichiers sources.
Par exemple, supposons que l’on dispose d’un module Tri qui permet de trier des tableaux
génériques (nous aborderons plus loin ce concept de généricité). Il est de la forme :
/* Tri.h */
/* Tri.c */
#ifndef __TRI__
#include "Tri.h"
#define __TRI__
...
void trier_tab(void **t, int n,
void trier_tab(void **t, int n,
int (*est_inf)(void *, void *)) {
int (*est_inf)(void *, void *));
...
...
}
...
#endif
119 / 399
Limiter les inclusions
On souhaite maintenant écrire un module TabInt pour gérer des tableaux d’entiers.
On n’écrira pas
/* TabInt.h */
#ifndef __TAB_INT__
#define __TAB_INT__ /* TabInt.c */
#include "TabInt.h"
#include "Tri.h"
int trier_tab_int(TabInt *t) {
typedef struct {int n; int *tab;} TabInt; /* Util. de ‘trier_tab‘ */
int trier_tab_int(TabInt *t); }
#endif
mais plutôt
/* TabInt.h */ /* TabInt.c */
#ifndef __TAB_INT__ #include "TabInt.h"
#define __TAB_INT__
#include "Tri.h"
typedef struct int n; int *tab; TabInt;
int trier_tab_int(TabInt *t); int trier_tab_int(TabInt *t) {
/* Util. de ‘trier_tab‘ */
#endif }
120 / 399
Dépendances étendues
Nous serons amenés dans la suite (dans le cadre de la compilation séparée) — étant donné un
fichier A.c — à considérer l’ensemble des fichiers d’en-tête qu’il inclut.
121 / 399
Dépendances étendues
Nous serons amenés dans la suite (dans le cadre de la compilation séparée) — étant donné un
fichier A.c — à considérer l’ensemble des fichiers d’en-tête qu’il inclut.
Si A.c inclut un fichier d’en-tête B.h , alors le module A dépend de manière étendue au module
B .
121 / 399
Dépendances étendues
Nous serons amenés dans la suite (dans le cadre de la compilation séparée) — étant donné un
fichier A.c — à considérer l’ensemble des fichiers d’en-tête qu’il inclut.
Si A.c inclut un fichier d’en-tête B.h , alors le module A dépend de manière étendue au module
B .
Le graphe d’inclusions étendu d’un projet consiste en le graphe d’inclusions du projet dans
lequel sont ajoutées des flèches en pointillés pour symboliser les dépendances étendues.
121 / 399
Dépendances étendues
Nous serons amenés dans la suite (dans le cadre de la compilation séparée) — étant donné un
fichier A.c — à considérer l’ensemble des fichiers d’en-tête qu’il inclut.
Si A.c inclut un fichier d’en-tête B.h , alors le module A dépend de manière étendue au module
B .
Le graphe d’inclusions étendu d’un projet consiste en le graphe d’inclusions du projet dans
lequel sont ajoutées des flèches en pointillés pour symboliser les dépendances étendues.
Note 1. : les flèches qui partent du module principal Main représentent des inclusions étendues.
121 / 399
Dépendances étendues
Nous serons amenés dans la suite (dans le cadre de la compilation séparée) — étant donné un
fichier A.c — à considérer l’ensemble des fichiers d’en-tête qu’il inclut.
Si A.c inclut un fichier d’en-tête B.h , alors le module A dépend de manière étendue au module
B .
Le graphe d’inclusions étendu d’un projet consiste en le graphe d’inclusions du projet dans
lequel sont ajoutées des flèches en pointillés pour symboliser les dépendances étendues.
Note 1. : les flèches qui partent du module principal Main représentent des inclusions étendues.
Note 2. : les cycles dans lesquels intervient au moins une flèche en pointillés ne posent pas de
problème de structure du projet.
121 / 399
Dépendances étendues
Par exemple, le graphe d’inclusions étendu
A B
F E
C D
I A.h n’inclut rien ; I C.h inclut A.h ; I E.h inclut B.h et D.h
On observe que ce projet n’est pas mal structuré car il ne possède pas de cycle formé uniquement
par des flèches de dépendance.
122 / 399
Plan
Modules
Notion de modularité
Découpage d’un projet
Fichiers sources / d’en-tête
Création de modules
Graphes d’inclusions
Erreurs courantes et bonnes habitudes
123 / 399
Erreur : le fichier d’en-tête général
Une erreur consiste, pour un projet donné, à développer un fichier Types.h et plusieurs fichiers
source F1.c , F2.c , . . ., Fn.c .
124 / 399
Erreur : le fichier d’en-tête général
Une erreur consiste, pour un projet donné, à développer un fichier Types.h et plusieurs fichiers
source F1.c , F2.c , . . ., Fn.c .
Ici le fichier d’en-tête Types.h contient les déclarations de tous les types et fonctions nécessaires
au projet et les fichiers sources Fi.c implantent chacun un sous-ensemble des fonctions
déclarées.
124 / 399
Erreur : le fichier d’en-tête général
Une erreur consiste, pour un projet donné, à développer un fichier Types.h et plusieurs fichiers
source F1.c , F2.c , . . ., Fn.c .
Ici le fichier d’en-tête Types.h contient les déclarations de tous les types et fonctions nécessaires
au projet et les fichiers sources Fi.c implantent chacun un sous-ensemble des fonctions
déclarées.
Cette conception est erronée puisque :
1. il n’y a plus de notion de module (et donc tous leurs avantages relatifs sont absents) ;
2. il est impossible de réutiliser du code du projet pour un nouveau (il faudrait copier / coller
les types et fonctions importantes, ce qui n’est pas abordable) ;
3. le fichier Types.h peut contenir des déclarations de types et de fonctions qui n’ont pas grand
chose à voir.
124 / 399
Erreur : économie d’inclusions
Une erreur consiste à éviter volontairement de réaliser des inclusions de modules dans d’autres si
l’inclusion est déjà réalisée de manière transitive.
125 / 399
Erreur : économie d’inclusions
Une erreur consiste à éviter volontairement de réaliser des inclusions de modules dans d’autres si
l’inclusion est déjà réalisée de manière transitive.
Plus explicitement, soient trois modules A , B et C tels que B inclut C , A inclut B et A inclut
C :
A B C
125 / 399
Erreur : économie d’inclusions
Une erreur consiste à éviter volontairement de réaliser des inclusions de modules dans d’autres si
l’inclusion est déjà réalisée de manière transitive.
Plus explicitement, soient trois modules A , B et C tels que B inclut C , A inclut B et A inclut
C :
A B C
On peut être tenté de n’inclure que B dans A et C dans B car — par transitivité — ceci
entraîne que C est inclut dans A . Ceci fonctionne en pratique.
125 / 399
Erreur : économie d’inclusions
Une erreur consiste à éviter volontairement de réaliser des inclusions de modules dans d’autres si
l’inclusion est déjà réalisée de manière transitive.
Plus explicitement, soient trois modules A , B et C tels que B inclut C , A inclut B et A inclut
C :
A B C
On peut être tenté de n’inclure que B dans A et C dans B car — par transitivité — ceci
entraîne que C est inclut dans A . Ceci fonctionne en pratique.
Cette conception est cependant erronée puisque :
1. savoir de quels modules dépend A simplement en lisant son fichier d’en-tête, sans avoir de surprise
sur les modules qui peuvent être inclus de manière cachée par transitivité, est un avantage ;
2. le jour où l’on modifie B de sorte qu’il n’ait plus besoin de dépendre de C provoque le fait que C
126 / 399
Habitude : un module par type
Une bonne façon de faire par défaut consiste à créer un module pour chaque type nécessaire à
l’écriture d’un projet.
Avec ce point de vue, il y a dans chaque A.h une déclaration de type unique (dont le nom est A ,
celui du module) et des déclarations de fonctions qui agissent sur des éléments de type A .
126 / 399
Habitude : un module par type
Une bonne façon de faire par défaut consiste à créer un module pour chaque type nécessaire à
l’écriture d’un projet.
Avec ce point de vue, il y a dans chaque A.h une déclaration de type unique (dont le nom est A ,
celui du module) et des déclarations de fonctions qui agissent sur des éléments de type A .
Cette conception est correcte mais un peu limitée car
1. elle dispense d’une reflexion approfondie sur un bon découpage en modules du projet ;
126 / 399
Habitude : un module par type
Une bonne façon de faire par défaut consiste à créer un module pour chaque type nécessaire à
l’écriture d’un projet.
Avec ce point de vue, il y a dans chaque A.h une déclaration de type unique (dont le nom est A ,
celui du module) et des déclarations de fonctions qui agissent sur des éléments de type A .
Cette conception est correcte mais un peu limitée car
1. elle dispense d’une reflexion approfondie sur un bon découpage en modules du projet ;
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
127 / 399
Plan
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
128 / 399
Compilation d’un projet d’un fichier
La compilation d’un projet constitué d’un unique fichier Fichier.c contenant la fonction
principale main se fait par la commande
gcc Fichier.c
129 / 399
Compilation d’un projet d’un fichier
La compilation d’un projet constitué d’un unique fichier Fichier.c contenant la fonction
principale main se fait par la commande
gcc Fichier.c
129 / 399
Compilation d’un projet d’un fichier
La compilation d’un projet constitué d’un unique fichier Fichier.c contenant la fonction
principale main se fait par la commande
gcc Fichier.c
129 / 399
Compilation d’un projet d’un fichier
La compilation d’un projet constitué d’un unique fichier Fichier.c contenant la fonction
principale main se fait par la commande
gcc Fichier.c
129 / 399
Compilation d’un projet d’un fichier
La compilation d’un projet constitué d’un unique fichier Fichier.c contenant la fonction
principale main se fait par la commande
gcc Fichier.c
129 / 399
Compilation d’un projet d’un fichier
Fichier.c
pré-processeur
exécutable
a.out
130 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
131 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
Il procède en
1. supprimant les commentaires ;
131 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
Il procède en
1. supprimant les commentaires ;
2. incluant les fichiers d’en-tête (copie / colle les fichiers .h inclus) ;
131 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
Il procède en
1. supprimant les commentaires ;
2. incluant les fichiers d’en-tête (copie / colle les fichiers .h inclus) ;
131 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
Il procède en
1. supprimant les commentaires ;
2. incluant les fichiers d’en-tête (copie / colle les fichiers .h inclus) ;
131 / 399
Pré-processeur
Le pré-processeur réalise un pré-traitement du fichier source pour le rendre traduisible en
langage machine.
Il procède en
1. supprimant les commentaires ;
2. incluant les fichiers d’en-tête (copie / colle les fichiers .h inclus) ;
131 / 399
Compilation en assembleur
Après avoir été traité par le pré-processeur, le fichier Fichier.i est traduit en assembleur.
132 / 399
Compilation en assembleur
Après avoir été traité par le pré-processeur, le fichier Fichier.i est traduit en assembleur.
gcc -S Fichier.c
132 / 399
Compilation en assembleur
Après avoir été traité par le pré-processeur, le fichier Fichier.i est traduit en assembleur.
gcc -S Fichier.c
L’assembleur est un langage très proche de la machine. Il peut se traduire assez facilement en un
langage directement exécutable par le procésseur.
Il existe plusieurs langages d’assemblage différents : au moins un par architecture.
132 / 399
Compilation en assembleur
Par exemple, avec le fichier Fichier.c suivant :
/* Fichier.c */
#include <stdio.h>
int main() {
printf("Bonjour");
return 0;
}
133 / 399
Traduction en langage machine
134 / 399
Traduction en langage machine
gcc -c Fichier.c
134 / 399
Traduction en langage machine
gcc -c Fichier.c
Ce fichier s’appelle fichier objet. Il est illisible pour un humain mais peut cependant être affiché
au moyen de la commande
od -x Fichier.o ou bien od -a Fichier.o
134 / 399
Traduction en langage machine
gcc -c Fichier.c
Ce fichier s’appelle fichier objet. Il est illisible pour un humain mais peut cependant être affiché
au moyen de la commande
od -x Fichier.o ou bien od -a Fichier.o
Le langage machine est directement compris par le processeur qui peut de ce fait exécuter
directement les instructions qu’il contient.
134 / 399
Traduction en langage machine
Par exemple, avec le programme précédent, le contenu de Fichier.o est
0000000 del E L F stx soh soh nul nul nul nul nul nul nul nul nul
0000020 soh nul > nul soh nul nul nul nul nul nul nul nul nul nul nul
0000040 nul nul nul nul nul nul nul nul 0 soh nul nul nul nul nul nul
0000060 nul nul nul nul @ nul nul nul nul nul @ nul cr nul nl nul
0000100 U H ht e ? nul nul nul nul 8 nul nul nul nul h nul
0000120 nul nul nul 8 nul nul nul nul ] C B o n j o u
0000140 r nul nul G C C : sp ( U b u n t u /
0000160 L i n a r o sp 4 . 8 . 1 - 1 0 u
0000200 b u n t u 9 ) sp 4 . 8 . 1 nul nul nul
0000220 dc4 nul nul nul nul nul nul nul soh z R nul soh x dle soh
0000240 esc ff bel bs dle soh nul nul fs nul nul nul fs nul nul nul
0000260 nul nul nul nul sub nul nul nul nul A so dle ack stx C cr
0000300 ack U ff bel bs nul nul nul nul . s y m t a b
0000320 nul . s t r t a b nul . s h s t r t
0000340 a b nul . r e l a . t e x t nul . d
0000360 a t a nul . b s s nul . r o d a t a
135 / 399
Traduction en langage machine
0000400 nul . c o m m e n t nul . n o t e .
0000420 G N U - s t a c k nul . r e l a .
0000440 e h _ f r a m e nul nul nul nul nul nul nul nul
0000460 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
*
0000560 sp nul nul nul soh nul nul nul ack nul nul nul nul nul nul nul
0000600 nul nul nul nul nul nul nul nul @ nul nul nul nul nul nul nul
0000620 sub nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0000640 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0000660 esc nul nul nul eot nul nul nul nul nul nul nul nul nul nul nul
0000700 nul nul nul nul nul nul nul nul dle enq nul nul nul nul nul nul
0000720 0 nul nul nul nul nul nul nul vt nul nul nul soh nul nul nul
0000740 bs nul nul nul nul nul nul nul can nul nul nul nul nul nul nul
0000760 & nul nul nul soh nul nul nul etx nul nul nul nul nul nul nul
0001000 nul nul nul nul nul nul nul nul Z nul nul nul nul nul nul nul
0001020 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001040 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
136 / 399
Traduction en langage machine
0001060 , nul nul nul bs nul nul nul etx nul nul nul nul nul nul nul
0001100 nul nul nul nul nul nul nul nul Z nul nul nul nul nul nul nul
0001120 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001140 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001160 1 nul nul nul soh nul nul nul stx nul nul nul nul nul nul nul
0001200 nul nul nul nul nul nul nul nul Z nul nul nul nul nul nul nul
0001220 bs nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001240 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001260 9 nul nul nul soh nul nul nul 0 nul nul nul nul nul nul nul
0001300 nul nul nul nul nul nul nul nul b nul nul nul nul nul nul nul
0001320 , nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001340 soh nul nul nul nul nul nul nul soh nul nul nul nul nul nul nul
0001360 B nul nul nul soh nul nul nul nul nul nul nul nul nul nul nul
0001400 nul nul nul nul nul nul nul nul so nul nul nul nul nul nul nul
0001420 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001440 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001460 W nul nul nul soh nul nul nul stx nul nul nul nul nul nul nul
137 / 399
Traduction en langage machine
0001500 nul nul nul nul nul nul nul nul dle nul nul nul nul nul nul nul
0001520 8 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001540 bs nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001560 R nul nul nul eot nul nul nul nul nul nul nul nul nul nul nul
0001600 nul nul nul nul nul nul nul nul @ enq nul nul nul nul nul nul
0001620 can nul nul nul nul nul nul nul vt nul nul nul bs nul nul nul
0001640 bs nul nul nul nul nul nul nul can nul nul nul nul nul nul nul
0001660 dc1 nul nul nul etx nul nul nul nul nul nul nul nul nul nul nul
0001700 nul nul nul nul nul nul nul nul H nul nul nul nul nul nul nul
0001720 a nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001740 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0001760 soh nul nul nul stx nul nul nul nul nul nul nul nul nul nul nul
0002000 nul nul nul nul nul nul nul nul p eot nul nul nul nul nul nul
0002020 bs soh nul nul nul nul nul nul ff nul nul nul ht nul nul nul
0002040 bs nul nul nul nul nul nul nul can nul nul nul nul nul nul nul
0002060 ht nul nul nul etx nul nul nul nul nul nul nul nul nul nul nul
0002100 nul nul nul nul nul nul nul nul x enq nul nul nul nul nul nul
138 / 399
Traduction en langage machine
0002120 etb nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002140 soh nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002160 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002200 nul nul nul nul nul nul nul nul soh nul nul nul eot nul q del
0002220 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002240 nul nul nul nul etx nul soh nul nul nul nul nul nul nul nul nul
0002260 nul nul nul nul nul nul nul nul nul nul nul nul etx nul etx nul
0002300 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002320 nul nul nul nul etx nul eot nul nul nul nul nul nul nul nul nul
0002340 nul nul nul nul nul nul nul nul nul nul nul nul etx nul enq nul
0002360 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002400 nul nul nul nul etx nul bel nul nul nul nul nul nul nul nul nul
0002420 nul nul nul nul nul nul nul nul nul nul nul nul etx nul bs nul
0002440 nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul nul
0002460 nul nul nul nul etx nul ack nul nul nul nul nul nul nul nul nul
0002500 nul nul nul nul nul nul nul nul vt nul nul nul dc2 nul soh nul
0002520 nul nul nul nul nul nul nul nul sub nul nul nul nul nul nul nul
139 / 399
Traduction en langage machine
0002540 dle nul nul nul dle nul nul nul nul nul nul nul nul nul nul nul
0002560 nul nul nul nul nul nul nul nul nul F i c h i e r
0002600 . c nul m a i n nul p r i n t f nul nul
0002620 enq nul nul nul nul nul nul nul nl nul nul nul enq nul nul nul
0002640 nul nul nul nul nul nul nul nul si nul nul nul nul nul nul nul
0002660 stx nul nul nul nl nul nul nul | del del del del del del del
0002700 sp nul nul nul nul nul nul nul stx nul nul nul stx nul nul nul
0002720 nul nul nul nul nul nul nul nul
0002730
140 / 399
Édition des liens
L’édition des liens réunit le fichier objet et le code propre aux fonctions et types de la librairie
standard utilisés (comme printf , scanf , etc.) pour produire l’exécutable complet.
141 / 399
Édition des liens
L’édition des liens réunit le fichier objet et le code propre aux fonctions et types de la librairie
standard utilisés (comme printf , scanf , etc.) pour produire l’exécutable complet.
Avant l’édition des liens, seuls les prototypes des fonctions sont connus du compilateur. Cela lui
permet de vérifier que les types sont bien respectés.
141 / 399
Édition des liens
L’édition des liens réunit le fichier objet et le code propre aux fonctions et types de la librairie
standard utilisés (comme printf , scanf , etc.) pour produire l’exécutable complet.
Avant l’édition des liens, seuls les prototypes des fonctions sont connus du compilateur. Cela lui
permet de vérifier que les types sont bien respectés.
Cependant, pour l’obtention finale de l’exécutable qui va suivre, il est nécessaire de connaître le
comportement des fonctions (c.-à-d. leur définition).
141 / 399
Édition des liens
L’édition des liens réunit le fichier objet et le code propre aux fonctions et types de la librairie
standard utilisés (comme printf , scanf , etc.) pour produire l’exécutable complet.
Avant l’édition des liens, seuls les prototypes des fonctions sont connus du compilateur. Cela lui
permet de vérifier que les types sont bien respectés.
Cependant, pour l’obtention finale de l’exécutable qui va suivre, il est nécessaire de connaître le
comportement des fonctions (c.-à-d. leur définition).
C’est précisément dans cette phase de la compilation que la résolution des symboles a lieu. C’est
l’étape qui consiste à associer aux identificateurs de fonctions leur implantation.
141 / 399
Compilation d’un projet de plusieurs fichiers
On suppose que l’on travaille sur un projet constitué de trois modules A , B et C et d’un fichier
principal Main.c contenant la fonction main .
A.h
A.c
B.h
B.c
C.h
C.c
Main.c
142 / 399
Compilation d’un projet de plusieurs fichiers
On suppose que l’on travaille sur un projet constitué de trois modules A , B et C et d’un fichier
principal Main.c contenant la fonction main .
La compilation de ce projet se réalise au moyen des étapes suivantes :
1. obtenir les fichiers objets de chaque module ;
Main.c
142 / 399
Compilation d’un projet de plusieurs fichiers
On suppose que l’on travaille sur un projet constitué de trois modules A , B et C et d’un fichier
principal Main.c contenant la fonction main .
La compilation de ce projet se réalise au moyen des étapes suivantes :
1. obtenir les fichiers objets de chaque module ;
2. obtenir le fichier objet de Main.c ;
gcc -c Main.c
Main.c Main.o
142 / 399
Compilation d’un projet de plusieurs fichiers
On suppose que l’on travaille sur un projet constitué de trois modules A , B et C et d’un fichier
principal Main.c contenant la fonction main .
La compilation de ce projet se réalise au moyen des étapes suivantes :
1. obtenir les fichiers objets de chaque module ;
2. obtenir le fichier objet de Main.c ;
3. lier les fichiers objets ainsi obtenus en un exécutable.
gcc -c Main.c
Main.c Main.o
142 / 399
Création des fichiers objets
Pour compiler le projet, on commence par créer un fichier objet pour chaque module M . On
utilise pour cela la commande
gcc -c M.c M.h
143 / 399
Création des fichiers objets
Pour compiler le projet, on commence par créer un fichier objet pour chaque module M . On
utilise pour cela la commande
gcc -c M.c M.h
143 / 399
Création des fichiers objets
Pour compiler le projet, on commence par créer un fichier objet pour chaque module M . On
utilise pour cela la commande
gcc -c M.c M.h
143 / 399
Création des fichiers objets
Pour compiler le projet, on commence par créer un fichier objet pour chaque module M . On
utilise pour cela la commande
gcc -c M.c M.h
143 / 399
Symboles non résolus
Rappel : pour compiler un module A , il n’est pas nécessaire que A ait connaissance des
définitions des symboles qu’il utilise.
Seules leurs déclarations sont suffisantes. Celles-ci se trouvent dans les fichiers d’en-tête
inclus dans A .
144 / 399
Symboles non résolus
Rappel : pour compiler un module A , il n’est pas nécessaire que A ait connaissance des
définitions des symboles qu’il utilise.
Seules leurs déclarations sont suffisantes. Celles-ci se trouvent dans les fichiers d’en-tête
inclus dans A .
On dit qu’un symbole n’est pas résolu à un stade donné de la compilation si sa définition n’est
pas encore connue.
144 / 399
Symboles non résolus
Rappel : pour compiler un module A , il n’est pas nécessaire que A ait connaissance des
définitions des symboles qu’il utilise.
Seules leurs déclarations sont suffisantes. Celles-ci se trouvent dans les fichiers d’en-tête
inclus dans A .
On dit qu’un symbole n’est pas résolu à un stade donné de la compilation si sa définition n’est
pas encore connue.
/* Fichier.c */ Par exemple, ce fichier permet de produire un fichier objet sur la com-
mande gcc -c Fichier.c même si le symbole g est non résolu pour le
int g(int x);
moment.
int f(int x) {
return g(x); Sa déclaration (dans le fichier lui-même ou dans un fichier inclus) est ce-
} pendant nécessaire (pour que le compilateur connaisse son prototype).
144 / 399
Résolution des symboles
Lors de l’édition des liens, un exécutable est créé. On utilise pour cela la commande
gcc Main.o A1.o ... An.o
dans le cadre d’un projet constitué des modules A1 , . . ., An et du module principal Main .
145 / 399
Résolution des symboles
Lors de l’édition des liens, un exécutable est créé. On utilise pour cela la commande
gcc Main.o A1.o ... An.o
dans le cadre d’un projet constitué des modules A1 , . . ., An et du module principal Main .
145 / 399
Résolution des symboles
Lors de l’édition des liens, un exécutable est créé. On utilise pour cela la commande
gcc Main.o A1.o ... An.o
dans le cadre d’un projet constitué des modules A1 , . . ., An et du module principal Main .
Tous les symboles utilisés dans le projet doivent être résolus (sinon, un message d’erreur est
produit et l’exécutable ne peut pas être construit).
145 / 399
Résolution des symboles — exemple
/∗ B. c ∗/ /∗ Main.c ∗/
/∗ A.h ∗/ /∗ A.c ∗/ /∗ B.h ∗/
#include "B.h" #include "B.h"
#ifndef __A__ #include "A.h" #ifndef __B__
#include "A.h" int main() {
#define __A__ int f(int x) { #define __B__
int g(int x) { g(5);
int f(int x); return x * x; int g(int x);
return f(x); return 0;
#endif } #endif
} }
146 / 399
Résolution des symboles — exemple
/∗ B. c ∗/ /∗ Main.c ∗/
/∗ A.h ∗/ /∗ A.c ∗/ /∗ B.h ∗/
#include "B.h" #include "B.h"
#ifndef __A__ #include "A.h" #ifndef __B__
#include "A.h" int main() {
#define __A__ int f(int x) { #define __B__
int g(int x) { g(5);
int f(int x); return x * x; int g(int x);
return f(x); return 0;
#endif } #endif
} }
gcc -c B.c
gcc -c Main.c
146 / 399
Résolution des symboles — exemple
/∗ B. c ∗/ /∗ Main.c ∗/
/∗ A.h ∗/ /∗ A.c ∗/ /∗ B.h ∗/
#include "B.h" #include "B.h"
#ifndef __A__ #include "A.h" #ifndef __B__
#include "A.h" int main() {
#define __A__ int f(int x) { #define __B__
int g(int x) { g(5);
int f(int x); return x * x; int g(int x);
return f(x); return 0;
#endif } #endif
} }
gcc -c B.c
gcc -c Main.c
L’ordre d’exécution des trois 1res commandes n’a aucune incidence sur le résultat produit.
146 / 399
Résolution des symboles — exemple
/∗ B. c ∗/ /∗ Main.c ∗/
/∗ A.h ∗/ /∗ A.c ∗/ /∗ B.h ∗/
#include "B.h" #include "B.h"
#ifndef __A__ #include "A.h" #ifndef __B__
#include "A.h" int main() {
#define __A__ int f(int x) { #define __B__
int g(int x) { g(5);
int f(int x); return x * x; int g(int x);
return f(x); return 0;
#endif } #endif
} }
Lors de la création de B.o , le compilateur ignore ce que fait le symbole f . Il sait seulement
(grâce à l’inclusion de A dans B ) que f est un symbole de fonction paramétrée par un entier et
renvoyant un entier et peut donc vérifier la correspondance des types.
C’est au moment de l’édition des liens que le compilateur va chercher l’implantation du
symbole f pour créer l’exécutable de la bonne manière.
147 / 399
Plan
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
148 / 399
Compilation séparée — intuition
Fait 1. : pour compiler un module A , il n’est pas nécessaire d’avoir les fichiers objets des autres
modules du projet dont A ne dépend pas (de manière étendue ou non).
149 / 399
Compilation séparée — intuition
Fait 1. : pour compiler un module A , il n’est pas nécessaire d’avoir les fichiers objets des autres
modules du projet dont A ne dépend pas (de manière étendue ou non).
149 / 399
Compilation séparée — intuition
Fait 1. : pour compiler un module A , il n’est pas nécessaire d’avoir les fichiers objets des autres
modules du projet dont A ne dépend pas (de manière étendue ou non).
Fait 2. : si A dépend de B et seul le fichier source de B a été modifié, il n’est pas nécessaire de
recompiler A .
149 / 399
Compilation séparée — intuition
Fait 1. : pour compiler un module A , il n’est pas nécessaire d’avoir les fichiers objets des autres
modules du projet dont A ne dépend pas (de manière étendue ou non).
Fait 2. : si A dépend de B et seul le fichier source de B a été modifié, il n’est pas nécessaire de
recompiler A .
149 / 399
Compilation séparée — intuition
Fait 1. : pour compiler un module A , il n’est pas nécessaire d’avoir les fichiers objets des autres
modules du projet dont A ne dépend pas (de manière étendue ou non).
Fait 2. : si A dépend de B et seul le fichier source de B a été modifié, il n’est pas nécessaire de
recompiler A .
Attention à ne pas oublier de recompiler le module principal Main si celui-ci dépend de manière
étendue à des modules modifiés.
149 / 399
Compilation séparée — exemple introductif
Considérons par exemple le projet suivant :
A gcc -c Main.c
gcc -c A.c
B Main
gcc -c B.c
C
gcc -c C.c
150 / 399
Compilation séparée — exemple introductif
Considérons par exemple le projet suivant :
A gcc -c Main.c
gcc -c A.c
B Main
gcc -c B.c
C
gcc -c C.c
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
151 / 399
Schéma opérationnel de la compilation d’un projet
Nous allons utiliser l’utilitaire make et les fichiers Makefile pour rendre cette procédure
automatique.
151 / 399
Plan
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
152 / 399
Fichiers Makefile
L’utilitaire make est paramétré par un fichier dont le nom est imposé :
« Makefile » ou « makefile ».
Il doit se trouver dans le répertoire de travail (là où se trouvent les autres fichiers du projet ou au
niveau des répertoires include et src ).
Ce fichier fait partie intégrante du projet.
CIBLE: DEPENDANCES
→ COMMANDE
.
.
.
→ COMMANDE
153 / 399
Fichiers Makefile et fonctionnement
Considérons le projet et le Makefile suivants.
Lorsque l’on exécute la commande make , make tente de résoudre la 1re règle (l. 1). Pour cela, il
doit résoudre ses dépendances. Ensuite, il exécute les commandes de la règle si la cible n’est pas à
jour.
Concrètement, pour pouvoir créer l’exécutable Prog , il est nécessaire que A.o , B.o et Main.o
soient à jour. Une fois qu’ils le sont, il suffit de procéder à l’édition des liens (l. 2).
154 / 399
Fichiers Makefile et fonctionnement
Pour savoir si une cible est à jour, make regarde les dépendances de la règle et :
155 / 399
Fichiers Makefile et fonctionnement
Pour savoir si une cible est à jour, make regarde les dépendances de la règle et :
I si la dépendance est la cible d’une autre règle, make procède récursivement à sa résolution ;
155 / 399
Fichiers Makefile et fonctionnement
Pour savoir si une cible est à jour, make regarde les dépendances de la règle et :
I si la dépendance est la cible d’une autre règle, make procède récursivement à sa résolution ;
I si la dépendance est le nom d’un fichier, make compare la date de dernière modification
de la cible par rapport à celle du fichier.
155 / 399
Fichiers Makefile et fonctionnement
Pour savoir si une cible est à jour, make regarde les dépendances de la règle et :
I si la dépendance est la cible d’une autre règle, make procède récursivement à sa résolution ;
I si la dépendance est le nom d’un fichier, make compare la date de dernière modification
de la cible par rapport à celle du fichier.
S’il y a au moins un fichier dans les dépendances avec une date supérieure à celle de la cible, les
commandes de la règle sont exécutées.
155 / 399
Fichiers Makefile et fonctionnement
Pour savoir si une cible est à jour, make regarde les dépendances de la règle et :
I si la dépendance est la cible d’une autre règle, make procède récursivement à sa résolution ;
I si la dépendance est le nom d’un fichier, make compare la date de dernière modification
de la cible par rapport à celle du fichier.
S’il y a au moins un fichier dans les dépendances avec une date supérieure à celle de la cible, les
commandes de la règle sont exécutées.
On peut imposer à make de commencer par la résolution de la règle dont la cible est CIBLE par
make CIBLE
155 / 399
Déclarations de types et dépendances
Soient A et B deux modules tels que B dépend (de manière étendue) à A , qu’un type T soit
déclaré dans A et que B utilise ce type.
Toute modification de A.h doit être suivie d’une nouvelle compilation de B . En effet, si la
déclaration de T a été modifiée, B doit être recompilé pour la prendre en compte.
Attention : ceci ne s’applique pas aux modifications de l’implantation des fonctions de A dans
A.c (comme nous l’avons déjà vu). La cible B.o ne dépend ainsi pas de A.c . Elle ne dépend que
des déclarations du module A (et donc de A.h ).
156 / 399
Exemple complet de Makefile
Piece IA
Le Makefile du projet dont le graphe
d’inclusions (étendues) est donné ci-contre est
Position Main
Case IGraph
où EXTRAmain est la suite des noms des fichiers .h que Main.c inclut et pour tout 16k6n ,
EXTRAk est la suite des noms des fichiers .h dont le module Ak dépend (de manière étendue).
158 / 399
Plan
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
159 / 399
Variables dans les Makefile
ID=VAL
Ceci définit une variable identifiée par ID . Sa valeur est la chaîne de caractères VAL .
$(ID)
Ceci substitue à l’occurrence de $(ID) la chaîne de caractères qui lui a été attribuée lors de sa
définition.
160 / 399
Variables dans les Makefile — exemple
Les variables permettent de factoriser les règles d’un Makefile :
162 / 399
Règles courantes
Observation : la plupart des règles des Makefile sont sous l’une de ces deux formes :
La 1re forme de règle a pour but de construire le fichier objet d’un module M . Dans ce cas, DEP2 ,
. . ., DEPn sont les .h dont le module M dépend.
La 2e forme de règle a pour but de construire l’exécutable EXEC du projet. Les dépendances DEP1 ,
. . ., DEPn sont dans ce cas les fichiers .o du projet.
163 / 399
Variables internes dans les Makefile
Il est possible de simplifier l’écriture de ces règles courantes au moyen des variables internes. Il y
en a trois principales et deux secondaires :
Symbole Ce qu’il désigne
$@ Nom de la cible
$< Nom de la 1re dép.
$^ Noms de toutes les dép.
$? Noms des dép. plus récentes que la cible
$* Nom de la cible sans extension
En les utilisant, les deux règles précédentes deviennent
M.o: M.c DEP2 ... DEPn
M.o: M.c DEP2 ... DEPn
−→ gcc -c $<
gcc -c M.c
164 / 399
Variables internes dans les Makefile — exemple
Piece IA L’utilisation des variables et variables internes
Position Main
permet de simplifier les Makefile .
Case IGraph
165 / 399
Règles génériques
Il est possible de simplifier encore d’avantage l’écriture des Makefile au moyen des règles
génériques.
Ce sont des règles de la forme
%.o: %.c
→ COMMANDE
Attention : la règle
%.o: %.c
gcc -c $<
ne prend pas en compte des dépendances des modules aux fichiers .h concernés.
pour chaque module M du projet. DEP1 , . . ., DEPn sont les .h dont le module M dépend.
167 / 399
Règles génériques — exemple
Piece IA L’utilisation des règles génériques permet de
Position Main
simplifier encore les Makefile .
Case IGraph
1 CC=colorgcc 15
2 CFLAGS=-ansi -pedantic -Wall 16 Position.o: Position.c Position.h
3 OBJ=Main.o Piece.o Case.o 17 Piece.h Case.h
4 Position.o IA.o IGraph.o 18
5 19 IA.o: IA.c IA.h
6 Echecs: $(OBJ) 20 Piece.h Case.h Position.h
7 $(CC) -o $@ $^ $(CFLAGS) 21
8 22 IGraph.o: IGraph.c IGraph.h
9 23 Piece.h Case.h Position.h
10 Main.o: Main.c IA.h IGraph.h 24
11 25 %.o: %.c
12 Piece.o: Piece.c Piece.h 26 $(CC) -c $< $(CFLAGS)
13
14 Case.o: Case.c Case.h
168 / 399
Règles de nettoyage
Règle de nettoyage :
169 / 399
Règles d’installation / désinstallation
Règle d’installation :
Règle de désinstallation :
170 / 399
Plan
Compilation
Étapes de compilation
Compilation séparée
Makefile simples
Makefile avancés
Bibliothèques
171 / 399
Principes généraux
Une bibliothèque est un regroupement de modules offrant des fonctionnalités allant vers un
même objectif.
172 / 399
Principes généraux
Une bibliothèque est un regroupement de modules offrant des fonctionnalités allant vers un
même objectif.
Par exemple, la bibliothèque graphique MLV est un ensemble de plusieurs modules ( MLV_audio ,
MLV_keyboard , MLV_image , etc.) réunis dans le but d’offrir des fonctions d’affichage graphique et de
gérer les entrées / sorties (son, clavier, souris, etc.).
172 / 399
Principes généraux
Une bibliothèque est un regroupement de modules offrant des fonctionnalités allant vers un
même objectif.
Par exemple, la bibliothèque graphique MLV est un ensemble de plusieurs modules ( MLV_audio ,
MLV_keyboard , MLV_image , etc.) réunis dans le but d’offrir des fonctions d’affichage graphique et de
gérer les entrées / sorties (son, clavier, souris, etc.).
172 / 399
Principes généraux
Une bibliothèque est un regroupement de modules offrant des fonctionnalités allant vers un
même objectif.
Par exemple, la bibliothèque graphique MLV est un ensemble de plusieurs modules ( MLV_audio ,
MLV_keyboard , MLV_image , etc.) réunis dans le but d’offrir des fonctions d’affichage graphique et de
gérer les entrées / sorties (son, clavier, souris, etc.).
172 / 399
Bibliothèques statiques
173 / 399
Bibliothèques statiques
Lors de son utilisation, son code est inclus dans l’exécutable pendant l’édition des liens.
173 / 399
Bibliothèques statiques
Lors de son utilisation, son code est inclus dans l’exécutable pendant l’édition des liens.
I Avantage : tout projet qui utilise une bibliothèque statique peut être exécuté sur une
machine où la bibliothèque n’est pas installée.
173 / 399
Bibliothèques statiques
Lors de son utilisation, son code est inclus dans l’exécutable pendant l’édition des liens.
I Avantage : tout projet qui utilise une bibliothèque statique peut être exécuté sur une
machine où la bibliothèque n’est pas installée.
I Inconvénient : l’exécutable est plus volumineux.
173 / 399
Bibliothèques dynamiques
174 / 399
Bibliothèques dynamiques
Lors de son utilisation, son code n’est pas inclus dans l’exécutable. C’est lors de l’exécution que
les symboles provenant de la bibliothèque sont résolus au moyen de l’éditeur de liens dynamique.
174 / 399
Bibliothèques dynamiques
Lors de son utilisation, son code n’est pas inclus dans l’exécutable. C’est lors de l’exécution que
les symboles provenant de la bibliothèque sont résolus au moyen de l’éditeur de liens dynamique.
174 / 399
Bibliothèques dynamiques
Lors de son utilisation, son code n’est pas inclus dans l’exécutable. C’est lors de l’exécution que
les symboles provenant de la bibliothèque sont résolus au moyen de l’éditeur de liens dynamique.
174 / 399
Compilation d’un projet utilisant des bibliothèques
On suppose que l’on travaille sur un projet constitué de deux modules A et B et d’un fichier
principal Main.c . Ce projet utilise deux bibliothèques statiques : libm.a et libMLV.a .
175 / 399
Compilation d’un projet utilisant des bibliothèques
On suppose que l’on travaille sur un projet constitué de deux modules A et B et d’un fichier
principal Main.c . Ce projet utilise deux bibliothèques statiques : libm.a et libMLV.a .
La compilation de ce projet se réalise de manière habituelle. La différence porte sur l’étape
d’édition des liens dans laquelle il est nécessaire de signaler les bibliothèques utilisées.
175 / 399
Compilation d’un projet utilisant des bibliothèques
On suppose que l’on travaille sur un projet constitué de deux modules A et B et d’un fichier
principal Main.c . Ce projet utilise deux bibliothèques statiques : libm.a et libMLV.a .
La compilation de ce projet se réalise de manière habituelle. La différence porte sur l’étape
d’édition des liens dans laquelle il est nécessaire de signaler les bibliothèques utilisées.
libm.a libMLV.a
A.h gcc -c A.c
A.o
A.c
gcc -c Main.c
Main.c Main.o
175 / 399
Compilation d’un projet utilisant des bibliothèques
On suppose que l’on travaille sur un projet constitué de deux modules A et B et d’un fichier
principal Main.c . Ce projet utilise deux bibliothèques statiques : libm.a et libMLV.a .
La compilation de ce projet se réalise de manière habituelle. La différence porte sur l’étape
d’édition des liens dans laquelle il est nécessaire de signaler les bibliothèques utilisées.
libm.a libMLV.a
A.h gcc -c A.c
A.o
A.c
gcc -c Main.c
Main.c Main.o
Pour utiliser des bibliothèques qui ne sont pas situées dans le répertoire standard des
bibliothèques, on précisera leur chemin chem lors de l’édition des liens au moyen de l’option
-Lchem .
175 / 399
Compilation d’un projet utilisant des bibliothèques
Le nom d’une bibliothèque commence par lib . On signale l’utilisation d’une bibliothèque à
l’éditeur de liens par -lNOM où libNOM est le nom de la bibliothèque.
Main
projet précédent est celui ci-contre, un Makefile possible B
est
1 CC=colorgcc 18 clean:
2 CFLAGS=-ansi -pedantic -Wall 19 rm -f *.o
3 LDFLAGS=-lm -lncurses 20
4 OBJ=Main.o A.o B.o 21 mrproper: clean
5 22 rm -f Projet
6 Projet: $(OBJ) 23
7 $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) 24 install: Projet
8 25 mkdir ../bin
9 Main.o: Main.c A.h B.h 26 mv Projet ../bin/Projet
10 27 make mrproper
11 A.o: A.c A.h 28
12 29 uninstall: mrproper
13 B.o: B.c B.h A.h 30 rm -f ../bin/Projet
14 31 rm -rf ../bin
15 %.o: %.c
16 $(CC) -c $^ $(CFLAGS)
17
176 / 399
La bibliothèque standard
La bibliothèque standard libc.a ( libc.so ) regroupe les vingt-quatre modules
177 / 399
La bibliothèque standard
La bibliothèque standard libc.a ( libc.so ) regroupe les vingt-quatre modules
Cette bibliothèque est liée implicitement lors de toute édition des liens.
177 / 399
La bibliothèque standard
La bibliothèque standard libc.a ( libc.so ) regroupe les vingt-quatre modules
Cette bibliothèque est liée implicitement lors de toute édition des liens.
Il est donc possible d’utiliser certains des modules de la bibliothèque standard simplement en les
incluant ( #include <NOM.h> ), sans avoir à utiliser l’option -lc .
177 / 399
La bibliothèque standard
La bibliothèque standard libc.a ( libc.so ) regroupe les vingt-quatre modules
Cette bibliothèque est liée implicitement lors de toute édition des liens.
Il est donc possible d’utiliser certains des modules de la bibliothèque standard simplement en les
incluant ( #include <NOM.h> ), sans avoir à utiliser l’option -lc .
L’utilisation de certains modules doit cependant s’accompagner de l’option -lX (comme -lm
177 / 399
Création de bibliothèques statiques
Pour créer une bibliothèque statique X , on suit les étapes suivantes :
1. écriture des modules M1 , . . ., Mn qui vont constituer la bibliothèque ;
#include "M1.h"
...
#include "Mn.h"
#endif
178 / 399
Création de bibliothèques statiques — exemple
179 / 399
Création de bibliothèques statiques — exemple
179 / 399
Création de bibliothèques statiques — exemple
179 / 399
Création de bibliothèques statiques — exemple
Pour créer de la bibliothèque algo , on saisit les commandes
gcc -c Liste.c ; gcc -c Arbre.c ; gcc -c Tri.c
ranlib libalgo.a
/∗ Algo . h ∗/
#ifndef __ALGO__
#define __ALGO__
#include "Liste.h"
#include "Arbre.h"
#include "Tri.h"
#endif
Pour utiliser la bibliothèque algo dans un fichier F d’un projet P , il faut inclure dans F son
en-tête, réaliser l’édition des liens de P avec l’option -lalgo et indiquer son chemin chem avec
l’option -Lchem .
180 / 399
Index
Il est possible de consulter l’index d’une bibliothèque statique LIB par la commande
nm -s libLIB.a
/∗ B.h ∗/ /∗ B. c ∗/
#ifndef __B__ #include "B.h"
/∗ A.h ∗/ /∗ A.c ∗/
#define __B__ int f(S s) {
#ifndef __A__ #include "A.h"
typedef int S; return s;
#define __A__ char h(int a) {
}
char h(int a); return a % 256;
int f(S s); char g(int a) {
#endif }
char g(int a); return a % 64;
#endif }
Création :
Indexe de l’archive:
gcc -c A.c ; gcc -c B.c h in A.o
f in B.o
g in B.o
ar r libAB.a A.o B.o
A.o:
ranlib libAB.a 0000000000000000 T h
B.o:
Affichage : 0000000000000000 T f
000000000000000c T g
nm -s libAB.a
181 / 399
Résumé de l’axe 1
Nous avons étudié plusieurs outils et posé des conventions pour le développement de projets.
Tout ce qui a été étudié dans cet axe est à appliquer dans la pratique.
182 / 399
Résumé de l’axe 1
Nous avons étudié plusieurs outils et posé des conventions pour le développement de projets.
1. Au niveau de la présentation du code :
I indentation ;
I choix des identificateurs ;
I documentation.
Tout ce qui a été étudié dans cet axe est à appliquer dans la pratique.
182 / 399
Résumé de l’axe 1
Nous avons étudié plusieurs outils et posé des conventions pour le développement de projets.
1. Au niveau de la présentation du code :
I indentation ;
I choix des identificateurs ;
I documentation.
Tout ce qui a été étudié dans cet axe est à appliquer dans la pratique.
182 / 399
Résumé de l’axe 1
Nous avons étudié plusieurs outils et posé des conventions pour le développement de projets.
1. Au niveau de la présentation du code :
I indentation ;
I choix des identificateurs ;
I documentation.
Tout ce qui a été étudié dans cet axe est à appliquer dans la pratique.
182 / 399
Axe 2 : comprendre les mécanismes de base
Allocation dynamique
Entrées et sorties
Types
Types structurés
183 / 399
Plan
Allocation dynamique
Pointeurs
Passage par adresse
Allocation dynamique
Tableaux dynamiques
184 / 399
Plan
Allocation dynamique
Pointeurs
Passage par adresse
Allocation dynamique
Tableaux dynamiques
185 / 399
La mémoire
Lors de l’exécution d’un programme, une portion (de taille variable) de la mémoire lui est dédiée.
Cette zone lui est exclusive. On l’appelle la mémoire.
186 / 399
La mémoire
Lors de l’exécution d’un programme, une portion (de taille variable) de la mémoire lui est dédiée.
Cette zone lui est exclusive. On l’appelle la mémoire.
On peut voir la mémoire comme un très grand tableau d’octets.
186 / 399
La mémoire
Lors de l’exécution d’un programme, une portion (de taille variable) de la mémoire lui est dédiée.
Cette zone lui est exclusive. On l’appelle la mémoire.
On peut voir la mémoire comme un très grand tableau d’octets.
Autre
Il y a d’autres zones (non repr. ici).
186 / 399
La mémoire
Lors de l’exécution d’un programme, une portion (de taille variable) de la mémoire lui est dédiée.
Cette zone lui est exclusive. On l’appelle la mémoire.
On peut voir la mémoire comme un très grand tableau d’octets.
Autre
Il y a d’autres zones (non repr. ici).
Connaître l’adresse d’une variable est suffisant pour la manipuler (c.-à-d., lire et modifier sa
valeur). L’objet qui permet de représenter et de manipuler des adresses est le pointeur.
187 / 399
Pointeurs
Connaître l’adresse d’une variable est suffisant pour la manipuler (c.-à-d., lire et modifier sa
valeur). L’objet qui permet de représenter et de manipuler des adresses est le pointeur.
187 / 399
Pointeurs
Intuitivement, un pointeur est une flèche munie d’un mode d’emploi, le tout pointant vers une
zone de la mémoire.
Le mode d’emploi décrit le type de la zone de la mémoire ainsi adressée en renseignant sur la
taille de la zone.
188 / 399
Pointeurs
Intuitivement, un pointeur est une flèche munie d’un mode d’emploi, le tout pointant vers une
zone de la mémoire.
Le mode d’emploi décrit le type de la zone de la mémoire ainsi adressée en renseignant sur la
taille de la zone.
1000
1001
1002
ptr_1 1003
valeur : 1002
type : int * 1004
1005
1006
188 / 399
Pointeurs
Intuitivement, un pointeur est une flèche munie d’un mode d’emploi, le tout pointant vers une
zone de la mémoire.
Le mode d’emploi décrit le type de la zone de la mémoire ainsi adressée en renseignant sur la
taille de la zone.
Si ptr_1 est un pointeur sur une donnée de Si ptr_2 est un pointeur sur une donnée de
type int (4 octets) : type short (2 octets) :
1000 1000
1001 1001
1002 1002
1005 1005
1006 1006
188 / 399
Déclaration de pointeurs
T *ptr;
189 / 399
Déclaration de pointeurs
T *ptr;
*ptr
189 / 399
Déclaration de pointeurs
T *ptr;
*ptr
ptr
189 / 399
Déclaration de pointeurs
T *ptr;
*ptr
ptr
Attention : le même symbole * est utilisé pour la déclaration d’un pointeur et pour accéder à la
valeur pointée. C’est une imperfection du langage C qui peut porter à confusion.
189 / 399
Manipulation de pointeurs
On suppose que ptr est un pointeur pointant sur une zone de la mémoire de type T .
190 / 399
Manipulation de pointeurs
On suppose que ptr est un pointeur pointant sur une zone de la mémoire de type T .
ptr = ADR;
190 / 399
Manipulation de pointeurs
On suppose que ptr est un pointeur pointant sur une zone de la mémoire de type T .
ptr = ADR;
*ptr = VAL;
De cette manière, la zone mémoire d’adresse ptr est modifiée et devient de valeur VAL .
190 / 399
Manipulation de pointeurs — exemple
× ×
1 /∗ (1) ∗/
2 int num1, num2; 10 20 ? ?
3 int *ptr1, *ptr2;
4 num1 = 10; num_1 num_2 ptr_1 ptr_2
5 num2 = 20;
6
7 /∗ (2) ∗/
8 ptr1 = &num1; 100 200 &num_1 &num_2
9 ptr2 = &num2;
10 *ptr1 = 100; num_1 num_2 ptr_1 ptr_2
11 *ptr2 = 200;
12
13 /∗ (3) ∗/
14 ptr2 = ptr1;
15 *ptr1 = 300; 400 200 &num_1 &num_1
16 *ptr2 = 400;
num_1 num_2 ptr_1 ptr_2
191 / 399
Pointeurs et tableaux statiques
Un tableau est un pointeur vers le 1er élément qui le constitue. Les autres éléments du tableau
sont contigus en mémoire et situés à des adresses plus élevées.
192 / 399
Pointeurs et tableaux statiques
Un tableau est un pointeur vers le 1er élément qui le constitue. Les autres éléments du tableau
sont contigus en mémoire et situés à des adresses plus élevées.
On déclare un tableau statique de taille N de valeurs de type T par
T tab[N];
1 octet
192 / 399
Pointeurs et tableaux statiques
Un tableau est un pointeur vers le 1er élément qui le constitue. Les autres éléments du tableau
sont contigus en mémoire et situés à des adresses plus élevées.
On déclare un tableau statique de taille N de valeurs de type T par
T tab[N];
1 octet
tab[i]
192 / 399
Pointeurs et tableaux statiques
Un tableau est un pointeur vers le 1er élément qui le constitue. Les autres éléments du tableau
sont contigus en mémoire et situés à des adresses plus élevées.
On déclare un tableau statique de taille N de valeurs de type T par
T tab[N];
1 octet
tab[i]
tab[i] = VAL;
192 / 399
Pointeurs et tableaux statiques
Sachant qu’un tableau tab est un pointeur et que ses éléments sont contigus en mémoire, la
syntaxe
tab[i]
est équivalente à
*(tab + i)
193 / 399
Pointeurs et tableaux statiques
Sachant qu’un tableau tab est un pointeur et que ses éléments sont contigus en mémoire, la
syntaxe
tab[i]
est équivalente à
*(tab + i)
Le type du pointeur tab permet de faire un décalage correct en fonction de la taille en mémoire
de ses éléments.
193 / 399
Pointeurs et tableaux statiques
Sachant qu’un tableau tab est un pointeur et que ses éléments sont contigus en mémoire, la
syntaxe
tab[i]
est équivalente à
*(tab + i)
Le type du pointeur tab permet de faire un décalage correct en fonction de la taille en mémoire
de ses éléments.
En effet, si ptr est un pointeur pointant sur une zone mémoire de type T , la valeur de
l’expression ptr + i dépend de la taille nécessaire pour représenter une valeur de type T (c.-à-d.
de sizeof(T) ).
193 / 399
Pointeurs et tableaux statiques
Considérons les instructions suivantes :
int tab[2];
char *ptr;
tab[0] = 300;
tab[1] = 60;
printf("%p %p\n",
tab + 0, tab + 1);
printf("%d %d\n",
tab[0], tab[1]);
194 / 399
Pointeurs et tableaux statiques
Considérons les instructions suivantes :
printf("%p %p\n",
tab + 0, tab + 1);
printf("%d %d\n",
tab[0], tab[1]);
194 / 399
Pointeurs et tableaux statiques
Considérons les instructions suivantes :
194 / 399
Plan
Allocation dynamique
Pointeurs
Passage par adresse
Allocation dynamique
Tableaux dynamiques
195 / 399
Passage par valeur — rappel
Nous savons qu’il est impossible de modifier la valeur d’un argument passé à une fonction car
celle-ci travaille sur une copie de sa valeur et non pas sur l’éventuelle variable figurant en
argument.
196 / 399
Passage par valeur — rappel
Nous savons qu’il est impossible de modifier la valeur d’un argument passé à une fonction car
celle-ci travaille sur une copie de sa valeur et non pas sur l’éventuelle variable figurant en
argument.
196 / 399
Passage par valeur — rappel
Nous savons qu’il est impossible de modifier la valeur d’un argument passé à une fonction car
celle-ci travaille sur une copie de sa valeur et non pas sur l’éventuelle variable figurant en
argument.
196 / 399
Passage par valeur — rappel
Nous savons qu’il est impossible de modifier la valeur d’un argument passé à une fonction car
celle-ci travaille sur une copie de sa valeur et non pas sur l’éventuelle variable figurant en
argument.
qui est considéré. La fonction travaille en effet avec la valeur issue de l’évaluation de
l’expression 2 * num + 1 qui est recopiée dans la variable locale (paramètre) nb.
196 / 399
Passage par adresse
Cependant, si l’on donne l’adresse et le type d’une zone mémoire (ce qui revient à donner un
pointeur vers la zone mémoire considérée), il devient possible de la modifier.
197 / 399
Passage par adresse
Cependant, si l’on donne l’adresse et le type d’une zone mémoire (ce qui revient à donner un
pointeur vers la zone mémoire considérée), il devient possible de la modifier.
197 / 399
Passage par adresse
Cependant, si l’on donne l’adresse et le type d’une zone mémoire (ce qui revient à donner un
pointeur vers la zone mémoire considérée), il devient possible de la modifier.
197 / 399
Passage par adresse
Cependant, si l’on donne l’adresse et le type d’une zone mémoire (ce qui revient à donner un
pointeur vers la zone mémoire considérée), il devient possible de la modifier.
197 / 399
Qualificateur const
Dans certains cas, un passage par adresse n’est pas fait pour modifier la valeur située à l’adresse
spécifiée.
198 / 399
Qualificateur const
Dans certains cas, un passage par adresse n’est pas fait pour modifier la valeur située à l’adresse
spécifiée.
P.ex., cette fonction affiche, sans la modifier, la void eto( char *chaine, char c) {
chaîne de caractères chaine en substituant des int i = 0;
while (chaine[i] != ’\0’) {
étoiles ’*’ aux caractères c . if (chaine[i] == c)
putchar(’*’);
else
putchar(chaine[i]);
i += 1;
}
}
198 / 399
Qualificateur const
Dans certains cas, un passage par adresse n’est pas fait pour modifier la valeur située à l’adresse
spécifiée.
P.ex., cette fonction affiche, sans la modifier, la void eto( char *chaine, char c) {
chaîne de caractères chaine en substituant des int i = 0;
while (chaine[i] != ’\0’) {
étoiles ’*’ aux caractères c . if (chaine[i] == c)
putchar(’*’);
else
putchar(chaine[i]);
i += 1;
On souhaite protéger chaine de toute }
modification sur son contenu. }
198 / 399
Qualificateur const
Dans certains cas, un passage par adresse n’est pas fait pour modifier la valeur située à l’adresse
spécifiée.
P.ex., cette fonction affiche, sans la modifier, la void eto(const char *chaine, char c) {
chaîne de caractères chaine en substituant des int i = 0;
while (chaine[i] != ’\0’) {
étoiles ’*’ aux caractères c . if (chaine[i] == c)
putchar(’*’);
else
putchar(chaine[i]);
i += 1;
On souhaite protéger chaine de toute }
modification sur son contenu. }
198 / 399
Qualificateur const
Ainsi, plus généralement,
const T *ID
déclare un paramètre ID , pointeur sur une zone mémoire de type T , dont le contenu est protégé
en écriture.
199 / 399
Qualificateur const
Ainsi, plus généralement,
const T *ID
déclare un paramètre ID , pointeur sur une zone mémoire de type T , dont le contenu est protégé
en écriture.
void exemple_1(const int *x) { Cette tentative directe pour modifier la valeur à
*x = 40; l’adresse x est sanctionnée par le compilateur par
} une erreur.
199 / 399
Qualificateur const
Ainsi, plus généralement,
const T *ID
déclare un paramètre ID , pointeur sur une zone mémoire de type T , dont le contenu est protégé
en écriture.
void exemple_1(const int *x) { Cette tentative directe pour modifier la valeur à
*x = 40; l’adresse x est sanctionnée par le compilateur par
} une erreur.
void exemple_2(const int *x) { Cette tentative détournée pour modifier la valeur à
int *tmp; l’adresse x est sanctionnée par le compilateur par
tmp = x; un avertissement. Il est ainsi possible de modifier
*tmp = 40; une valeur protégée.
}
199 / 399
Qualificateur const
Ainsi, plus généralement,
const T *ID
déclare un paramètre ID , pointeur sur une zone mémoire de type T , dont le contenu est protégé
en écriture.
void exemple_1(const int *x) { Cette tentative directe pour modifier la valeur à
*x = 40; l’adresse x est sanctionnée par le compilateur par
} une erreur.
void exemple_2(const int *x) { Cette tentative détournée pour modifier la valeur à
int *tmp; l’adresse x est sanctionnée par le compilateur par
tmp = x; un avertissement. Il est ainsi possible de modifier
*tmp = 40; une valeur protégée.
}
Le qualificateur const est avant tout une aide pour le développeur : il informe d’un
comportement à adopter.
199 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
Voici un exemple qui illustre les quatre cas de figure :
int e;
} 200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
Voici un exemple qui illustre les quatre cas de figure :
int e;
a = &e; /* Autorise */
*a = 3; /* Autorise */
} 200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
Voici un exemple qui illustre les quatre cas de figure :
int e;
a = &e; /* Autorise */
*a = 3; /* Autorise */
} 200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
Voici un exemple qui illustre les quatre cas de figure :
a = &e; /* Autorise */
*a = 3; /* Autorise */
} 200 / 399
Qualificateur const
Le qualificateur const permet quelques subtilités :
T *const ID
déclare un paramètre ID , pointeur fixe sur une zone mémoire de type T , dont le contenu est
protégé en écriture.
Voici un exemple qui illustre les quatre cas de figure :
Allocation dynamique
Pointeurs
Passage par adresse
Allocation dynamique
Tableaux dynamiques
201 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
202 / 399
Données persistantes en mémoire
Nous savons que toutes les variables locales à une fonction n’existent plus après son appel.
Pour créer des données persistantes dans une fonction, il faut écrire dans la mémoire ailleurs que
dans la pile.
Elle renvoie un pointeur de type générique void * sur une donnée en mémoire de taille size
octets.
202 / 399
Utilisation de malloc
Pour allouer une zone de la mémoire pouvant accueillir N valeurs d’un type T , on utilise
l’instruction
ptr = (T *) malloc(sizeof(T) * N);
203 / 399
Utilisation de malloc
Pour allouer une zone de la mémoire pouvant accueillir N valeurs d’un type T , on utilise
l’instruction
ptr = (T *) malloc(sizeof(T) * N);
Explications :
I (T *) sert à préciser que la zone de la mémoire à allouer est de type T . Cette précision est
nécessaire car, par défaut, malloc renvoie un pointeur sur une zone non typée ( void * );
203 / 399
Utilisation de malloc
Pour allouer une zone de la mémoire pouvant accueillir N valeurs d’un type T , on utilise
l’instruction
ptr = (T *) malloc(sizeof(T) * N);
Explications :
I (T *) sert à préciser que la zone de la mémoire à allouer est de type T . Cette précision est
nécessaire car, par défaut, malloc renvoie un pointeur sur une zone non typée ( void * );
I l’argument sizeof(T) * N permet de demander à allouer une zone mémoire de taille
sizeof(T) * N octets. Celle-ci pourra donc accueillir N valeurs de type T .
203 / 399
Utilisation de malloc
Pour allouer une zone de la mémoire pouvant accueillir N valeurs d’un type T , on utilise
l’instruction
ptr = (T *) malloc(sizeof(T) * N);
Explications :
I (T *) sert à préciser que la zone de la mémoire à allouer est de type T . Cette précision est
nécessaire car, par défaut, malloc renvoie un pointeur sur une zone non typée ( void * );
I l’argument sizeof(T) * N permet de demander à allouer une zone mémoire de taille
sizeof(T) * N octets. Celle-ci pourra donc accueillir N valeurs de type T .
Après exécution de cette instruction, ptr pointe sur le début d’une zone de la mémoire de taille
sizeof(T) * N octets.
203 / 399
Tests d’erreurs et malloc
C’est le système d’exploitation qui, lors de l’appel à malloc se charge de fournir une adresse pour
la zone mémoire à allouer.
204 / 399
Tests d’erreurs et malloc
C’est le système d’exploitation qui, lors de l’appel à malloc se charge de fournir une adresse pour
la zone mémoire à allouer.
Il se peut que pour une raison ou une autre, il ne soit pas possible d’allouer la zone mémoire
demandée. Dans ce cas, malloc renvoie une valeur spéciale : NULL . Cette valeur vaut 0 et est une
constante définie dans stdlib.h .
204 / 399
Tests d’erreurs et malloc
C’est le système d’exploitation qui, lors de l’appel à malloc se charge de fournir une adresse pour
la zone mémoire à allouer.
Il se peut que pour une raison ou une autre, il ne soit pas possible d’allouer la zone mémoire
demandée. Dans ce cas, malloc renvoie une valeur spéciale : NULL . Cette valeur vaut 0 et est une
constante définie dans stdlib.h .
Il est impératif de tester, pour toute allocation dynamique réalisée, son bon déroulement. On
procède de la manière suivante :
char *ptr;
ptr = (char *) malloc(sizeof(char) * 1);
if (NULL == ptr)
ACTION
204 / 399
Tests d’erreurs et malloc
C’est le système d’exploitation qui, lors de l’appel à malloc se charge de fournir une adresse pour
la zone mémoire à allouer.
Il se peut que pour une raison ou une autre, il ne soit pas possible d’allouer la zone mémoire
demandée. Dans ce cas, malloc renvoie une valeur spéciale : NULL . Cette valeur vaut 0 et est une
constante définie dans stdlib.h .
Il est impératif de tester, pour toute allocation dynamique réalisée, son bon déroulement. On
procède de la manière suivante :
char *ptr;
ptr = (char *) malloc(sizeof(char) * 1);
if (NULL == ptr)
ACTION
De cette manière, si l’allocation s’est mal passée, on prend en charge cette erreur par les
instructions ACTION .
204 / 399
Tests d’erreurs et malloc
C’est le système d’exploitation qui, lors de l’appel à malloc se charge de fournir une adresse pour
la zone mémoire à allouer.
Il se peut que pour une raison ou une autre, il ne soit pas possible d’allouer la zone mémoire
demandée. Dans ce cas, malloc renvoie une valeur spéciale : NULL . Cette valeur vaut 0 et est une
constante définie dans stdlib.h .
Il est impératif de tester, pour toute allocation dynamique réalisée, son bon déroulement. On
procède de la manière suivante :
char *ptr;
ptr = (char *) malloc(sizeof(char) * 1);
if (NULL == ptr)
ACTION
De cette manière, si l’allocation s’est mal passée, on prend en charge cette erreur par les
instructions ACTION .
ACTION peut être un exit(EXIT_FAILURE); si l’on se trouve dans le main ou un return NULL; si l’on se
trouve dans une fonction.
204 / 399
Libération de mémoire
Pour désallouer une zone de la mémoire, on utilise la fonction
void free(void *ptr);
205 / 399
Libération de mémoire
Pour désallouer une zone de la mémoire, on utilise la fonction
void free(void *ptr);
ptr = NULL;
où ptr est un pointeur. La 2e ligne sert à ne plus conserver l’accès sur la zone libéré (non
indispensable mais peut éviter des erreurs).
205 / 399
Libération de mémoire
Pour désallouer une zone de la mémoire, on utilise la fonction
void free(void *ptr);
ptr = NULL;
où ptr est un pointeur. La 2e ligne sert à ne plus conserver l’accès sur la zone libéré (non
indispensable mais peut éviter des erreurs).
205 / 399
Libération de mémoire
Pour désallouer une zone de la mémoire, on utilise la fonction
void free(void *ptr);
ptr = NULL;
où ptr est un pointeur. La 2e ligne sert à ne plus conserver l’accès sur la zone libéré (non
indispensable mais peut éviter des erreurs).
Important : pour éviter les fuites mémoire, il faut désallouer toute zone de la mémoire qui n’est
plus utilisée.
205 / 399
La fonction calloc
La fonction
void *calloc(size_t nmemb, size_t size);
alloue et renvoie un pointeur sur une zone de la mémoire de nmemb * size octets, tous initialisés
avec des 0 .
206 / 399
La fonction calloc
La fonction
void *calloc(size_t nmemb, size_t size);
alloue et renvoie un pointeur sur une zone de la mémoire de nmemb * size octets, tous initialisés
avec des 0 .
P.ex.,
long *ptr;
ptr = (long *) calloc(13, sizeof(long));
alloue une zone de la mémoire pouvant accueillir 13 valeurs de type long , initialisées à 0 .
206 / 399
Plan
Allocation dynamique
Pointeurs
Passage par adresse
Allocation dynamique
Tableaux dynamiques
207 / 399
Tableaux dynamiques
Un tableau dynamique de N valeurs de type T est un pointeur pointant vers une zone de la
mémoire de sizeof(T) * N octets.
208 / 399
Tableaux dynamiques
Un tableau dynamique de N valeurs de type T est un pointeur pointant vers une zone de la
mémoire de sizeof(T) * N octets.
P.ex.,
int *tab;
tab = (int *) malloc(sizeof(int) * 66);
208 / 399
Tableaux dynamiques
Un tableau dynamique de N valeurs de type T est un pointeur pointant vers une zone de la
mémoire de sizeof(T) * N octets.
P.ex.,
int *tab;
tab = (int *) malloc(sizeof(int) * 66);
On lit et écrit dans un tableau dynamique de la même manière que dans un tableau statique. En
effet, tab pointe vers la première case du tableau, et pour tout 0 6 i < 66 , (tab + i) pointe
vers la case d’indice i .
208 / 399
Tableaux dynamiques
Un tableau dynamique de N valeurs de type T est un pointeur pointant vers une zone de la
mémoire de sizeof(T) * N octets.
P.ex.,
int *tab;
tab = (int *) malloc(sizeof(int) * 66);
On lit et écrit dans un tableau dynamique de la même manière que dans un tableau statique. En
effet, tab pointe vers la première case du tableau, et pour tout 0 6 i < 66 , (tab + i) pointe
vers la case d’indice i .
pour libérer la zone mémoire occupée par tab (c.-à-d. les 66 entiers situés à partir de l’adresse
tab ).
208 / 399
Création d’un tableau dynamique à deux dimensions
Un tableau à deux dimensions est un tableau dont chaque case est elle-même un tableau.
209 / 399
Création d’un tableau dynamique à deux dimensions
Un tableau à deux dimensions est un tableau dont chaque case est elle-même un tableau. Un
tableau dynamique à deux dimensions est donc un pointeur sur un pointeur.
209 / 399
Création d’un tableau dynamique à deux dimensions
Un tableau à deux dimensions est un tableau dont chaque case est elle-même un tableau. Un
tableau dynamique à deux dimensions est donc un pointeur sur un pointeur. Ceci se
généralise dans le cas des tableaux à plus de deux dimensions.
209 / 399
Création d’un tableau dynamique à deux dimensions
Un tableau à deux dimensions est un tableau dont chaque case est elle-même un tableau. Un
tableau dynamique à deux dimensions est donc un pointeur sur un pointeur. Ceci se
généralise dans le cas des tableaux à plus de deux dimensions.
Les instructions
int i, j, **tab;
tab = (int **) malloc(sizeof(int *) * 4);
if (tab == NULL) exit(EXIT_FAILURE);
for (i = 0 ; i < 4 ; ++i) {
tab[i] = (int *) malloc(sizeof(int) * 3);
if (tab[i] == NULL) exit(EXIT_FAILURE);
for (j = 0 ; j < 3 ; ++j)
tab[i][j] = 0;
}
209 / 399
Création d’un tableau dynamique à deux dimensions
tab × Initialement, tab est pointeur vers une zone
indéterminée de la mémoire.
210 / 399
Création d’un tableau dynamique à deux dimensions
tab × Initialement, tab est pointeur vers une zone
indéterminée de la mémoire.
tab
Juste après la 1re allocation dynamique, tab est
tab[0] ×
dans cet état. L’espace de mémoire de 4
tab[1] ×
pointeurs sur des entiers a été réservé.
tab[2] ×
tab[3] ×
210 / 399
Création d’un tableau dynamique à deux dimensions
tab × Initialement, tab est pointeur vers une zone
indéterminée de la mémoire.
tab
Juste après la 1re allocation dynamique, tab est
tab[0] ×
dans cet état. L’espace de mémoire de 4
tab[1] ×
pointeurs sur des entiers a été réservé.
tab[2] ×
tab[3] ×
tab[0][0] tab[0][2]
0 0 0
Après les allocations dynamiques des tableaux
tab à une dimension de taille 3 et des
tab[0]
initialisations des cases d’entiers, tab est dans
0 0 0
tab[1]
cet état.
tab[2][1]
tab[2]
0 0 0
tab[3]
0 0 0
210 / 399
Destruction d’un tableau dynamique à deux dimensions
Pour libérer l’espace occupé par un tableau à deux dimensions, on utilise plusieurs fois la
fonction free .
211 / 399
Destruction d’un tableau dynamique à deux dimensions
Pour libérer l’espace occupé par un tableau à deux dimensions, on utilise plusieurs fois la
fonction free .
Les instructions
for (i = 0 ; i < 4 ; ++i) {
free(tab[i]);
tab[i] = NULL;
}
free(tab);
tab = NULL;
permettent de libérer l’espace mémoire occupé par un tableau tab à deux dimensions de taille 4
211 / 399
Destruction d’un tableau dynamique à deux dimensions
Pour libérer l’espace occupé par un tableau à deux dimensions, on utilise plusieurs fois la
fonction free .
Les instructions
for (i = 0 ; i < 4 ; ++i) {
free(tab[i]);
tab[i] = NULL;
}
free(tab);
tab = NULL;
permettent de libérer l’espace mémoire occupé par un tableau tab à deux dimensions de taille 4
211 / 399
La fonction realloc
Il est possible de modifier la taille d’un tableau dynamique via la fonction
void *realloc(void *ptr, size_t size);
212 / 399
La fonction realloc
Il est possible de modifier la taille d’un tableau dynamique via la fonction
void *realloc(void *ptr, size_t size);
Celle-ci renvoie un pointeur sur une zone de la mémoire de taille size octets. Le contenu de cette
zone mémoire est le même que celui de la zone pointée par ptr .
212 / 399
La fonction realloc
Il est possible de modifier la taille d’un tableau dynamique via la fonction
void *realloc(void *ptr, size_t size);
Celle-ci renvoie un pointeur sur une zone de la mémoire de taille size octets. Le contenu de cette
zone mémoire est le même que celui de la zone pointée par ptr .
Attention : l’adresse de la zone de la mémoire réallouée peut être différente de son adresse
d’origine.
212 / 399
La fonction realloc
Il est possible de modifier la taille d’un tableau dynamique via la fonction
void *realloc(void *ptr, size_t size);
Celle-ci renvoie un pointeur sur une zone de la mémoire de taille size octets. Le contenu de cette
zone mémoire est le même que celui de la zone pointée par ptr .
Attention : l’adresse de la zone de la mémoire réallouée peut être différente de son adresse
d’origine. En effet,
int *tab;
tab = (int *) malloc(sizeof(int) * 150);
printf("%p\n", tab);
tab = (int *) realloc(tab, sizeof(int) * 250000);
printf("%p\n", tab);
affiche
0x1205010
0x7fb123f9d010
.
212 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Exemple d’utilisation de realloc
Construction d’une chaîne de caractères, caractère par caractère, dans un tableau dynamique. La
lecture s’arrête sur lecture de ’!’ .
213 / 399
Plan
Entrées et sorties
Sortie
Entrée
Fichiers
Fichiers binaires
214 / 399
Plan
Entrées et sorties
Sortie
Entrée
Fichiers
Fichiers binaires
215 / 399
Écriture formatée
La fonction
int printf(char *format, V_1, ..., V_N);
216 / 399
Écriture formatée
La fonction
int printf(char *format, V_1, ..., V_N);
216 / 399
Écriture formatée
La fonction
int printf(char *format, V_1, ..., V_N);
216 / 399
Indicateurs de conversion
On utilise les indicateurs de conversion suivants pour interpréter chaque valeur à écrire (ou à lire,
voir la partie suivante) de manière adéquate :
217 / 399
Caractères spéciaux
Certains caractères ne s’affichent pas mais produisent un effet sur la sortie. Ce sont des
caractères spéciaux.
218 / 399
Caractères d’attribut
Il est possible de faire suivre le % d’un indicateur de conversion de caractères d’attribut pour
réaliser un formatage avancé.
219 / 399
Caractères d’attribut
Il est possible de faire suivre le % d’un indicateur de conversion de caractères d’attribut pour
réaliser un formatage avancé.
P.ex.,
printf("%+5d\n", 23);
affiche ␣␣+23
219 / 399
Caractères d’attribut
Il est possible de faire suivre le % d’un indicateur de conversion de caractères d’attribut pour
réaliser un formatage avancé.
P.ex.,
printf("%+5d\n", 23); printf("%+-5d %d\n", 23, 5);
affiche ␣␣+23 et affiche +23␣␣␣5 .
219 / 399
Écriture caractère par caractère
La fonction
int putchar(int c);
220 / 399
Écriture caractère par caractère
La fonction
int putchar(int c);
220 / 399
Écriture caractère par caractère
La fonction
int putchar(int c);
220 / 399
Plan
Entrées et sorties
Sortie
Entrée
Fichiers
Fichiers binaires
221 / 399
Lecture formatée
La fonction
int scanf (char *format, PTR_1, ..., PTR_N);
222 / 399
Lecture formatée
La fonction
int scanf (char *format, PTR_1, ..., PTR_N);
222 / 399
Lecture formatée
La fonction
int scanf (char *format, PTR_1, ..., PTR_N);
223 / 399
Exemple d’utilisation de scanf
Ce programme lit sur l’entrée standard une chaîne d’au plus sept caractères (format %Ns ), une
espace, puis un entier.
223 / 399
Exemple d’utilisation de scanf
Ce programme lit sur l’entrée standard une chaîne d’au plus sept caractères (format %Ns ), une
espace, puis un entier.
223 / 399
Exemple d’utilisation de scanf
Ce programme lit sur l’entrée standard une chaîne d’au plus sept caractères (format %Ns ), une
espace, puis un entier.
#include <stdio.h>
#include <string.h>
int main() {
char prenom[8];
int age, ret;
223 / 399
Lecture caractère par caractère
La fonction
int getchar();
224 / 399
Lecture caractère par caractère
La fonction
int getchar();
224 / 399
Lecture caractère par caractère
La fonction
int getchar();
224 / 399
Le tampon d’entrée
Considérons les instructions
char c;
while ((c = getchar()) != EOF)
printf("%c", c);
225 / 399
Le tampon d’entrée
Considérons les instructions
char c;
while ((c = getchar()) != EOF)
printf("%c", c);
225 / 399
Le tampon d’entrée
Considérons les instructions
char c;
while ((c = getchar()) != EOF)
printf("%c", c);
Avant d’être lus par le getchar de la boucle, ils sont situés temporairement dans le tampon
d’entrée.
225 / 399
Le tampon d’entrée
Considérons les instructions
char c;
while ((c = getchar()) != EOF)
printf("%c", c);
Avant d’être lus par le getchar de la boucle, ils sont situés temporairement dans le tampon
d’entrée.
225 / 399
Le tampon d’entrée
Considérons les instructions
char c;
while ((c = getchar()) != EOF)
printf("%c", c);
Avant d’être lus par le getchar de la boucle, ils sont situés temporairement dans le tampon
d’entrée.
Entrées et sorties
Sortie
Entrée
Fichiers
Fichiers binaires
226 / 399
Descripteurs de fichiers et tête de lecture/écriture
Tout fichier est manipulé par un pointeur sur une variable de type FILE .
C’est un type structuré déclaré dans stdio.h qui contient diverses informations nécessaires et
relatives au fichier qu’il adresse.
Toute variable de type FILE * est un descripteur de fichier.
227 / 399
Descripteurs de fichiers et tête de lecture/écriture
Tout fichier est manipulé par un pointeur sur une variable de type FILE .
C’est un type structuré déclaré dans stdio.h qui contient diverses informations nécessaires et
relatives au fichier qu’il adresse.
Toute variable de type FILE * est un descripteur de fichier.
227 / 399
Tête de lecture/écriture
Il existe trois fonctions de stdio.h pour déplacer manuellement la tête de lecture ou obtenir des
informations à son sujet :
I int ftell(FILE *f); qui renvoie l’indice de la tête de lecture du fichier pointé par f .
Cette fonction est sans effet de bord ;
228 / 399
Tête de lecture/écriture
Il existe trois fonctions de stdio.h pour déplacer manuellement la tête de lecture ou obtenir des
informations à son sujet :
I int ftell(FILE *f); qui renvoie l’indice de la tête de lecture du fichier pointé par f .
Cette fonction est sans effet de bord ;
I int fseek(FILE *f, int decalage, int mode); qui décale la tête de lecture du fichier pointé par
f de decalage caractères selon le mode mode .
Ce paramètre explique à quoi le décalage est relatif ( SEEK_SET : début du fichier, SEEK_CUR :
position courante, SEEK_END : fin du fichier).
Cette fonction renvoie 0 si elle s’est bien exécutée et -1 sinon ;
228 / 399
Tête de lecture/écriture
Il existe trois fonctions de stdio.h pour déplacer manuellement la tête de lecture ou obtenir des
informations à son sujet :
I int ftell(FILE *f); qui renvoie l’indice de la tête de lecture du fichier pointé par f .
Cette fonction est sans effet de bord ;
I int fseek(FILE *f, int decalage, int mode); qui décale la tête de lecture du fichier pointé par
f de decalage caractères selon le mode mode .
Ce paramètre explique à quoi le décalage est relatif ( SEEK_SET : début du fichier, SEEK_CUR :
position courante, SEEK_END : fin du fichier).
Cette fonction renvoie 0 si elle s’est bien exécutée et -1 sinon ;
I void rewind(FILE *f) ; place la tête de lecture au début du fichier. L’appel rewind(f) est ainsi
équivalent à fseek(f, O, SEEK_SET) .
228 / 399
Ouverture de fichiers
La fonction
FILE *fopen(const char *chemin, const char *mode);
229 / 399
Ouverture de fichiers
La fonction
FILE *fopen(const char *chemin, const char *mode);
229 / 399
Ouverture de fichiers
La fonction
FILE *fopen(const char *chemin, const char *mode);
229 / 399
Ouverture de fichiers
La fonction
FILE *fopen(const char *chemin, const char *mode);
229 / 399
Ouverture de fichiers
La fonction
FILE *fopen(const char *chemin, const char *mode);
Attention : fopen renvoie NULL si l’ouverture s’est mal passée. Il faut donc toujours tester la
valeur de retour de fopen .
229 / 399
Modes d’ouverture
Il existe plusieurs modes d’ouverture. Chacun répond à un besoin particulier :
I "r" : lecture seule.
I "a" : écriture en ajout. Permet d’écrire dans le fichier en partant de la fin. Si le fichier
n’existe pas, il est créé.
I "r+" : lecture et écriture.
230 / 399
Fermeture de fichiers
La fonction
int fclose(FILE *f);
231 / 399
Fermeture de fichiers
La fonction
int fclose(FILE *f);
Cette fonction permet de mettre à jour le fichier pointé par f de sorte que toutes les
modifications effectuées soient prisent en compte. Le pointeur f n’est alors plus utilisable pour
accéder au fichier.
231 / 399
Fermeture de fichiers
La fonction
int fclose(FILE *f);
Cette fonction permet de mettre à jour le fichier pointé par f de sorte que toutes les
modifications effectuées soient prisent en compte. Le pointeur f n’est alors plus utilisable pour
accéder au fichier.
Cette fonction renvoie 0 si la fermeture s’est bien déroulée et EOF dans le cas contraire.
231 / 399
Fermeture de fichiers
La fonction
int fclose(FILE *f);
Cette fonction permet de mettre à jour le fichier pointé par f de sorte que toutes les
modifications effectuées soient prisent en compte. Le pointeur f n’est alors plus utilisable pour
accéder au fichier.
Cette fonction renvoie 0 si la fermeture s’est bien déroulée et EOF dans le cas contraire.
Attention : toute ouverture d’un fichier lors de l’exécution d’un programme doit s’accompagner
tôt ou tard de la fermeture future du fichier en question.
231 / 399
Écriture dans un fichier
La fonction
int fprintf(FILE *f, char *format, V_1, ..., V_N);
232 / 399
Écriture dans un fichier
La fonction
int fprintf(FILE *f, char *format, V_1, ..., V_N);
Cette fonction se comporte comme printf , à la différence que cette dernière travaille sur le
fichier stdout .
232 / 399
Écriture dans un fichier
La fonction
int fprintf(FILE *f, char *format, V_1, ..., V_N);
Cette fonction se comporte comme printf , à la différence que cette dernière travaille sur le
fichier stdout .
232 / 399
Lecture dans un fichier
La fonction
int fscanf(FILE *f, char *format, V_1, ..., V_N);
233 / 399
Lecture dans un fichier
La fonction
int fscanf(FILE *f, char *format, V_1, ..., V_N);
Cette fonction se comporte comme scanf , à la différence que cette dernière travaille sur le fichier
stdin .
233 / 399
Plan
Entrées et sorties
Sortie
Entrée
Fichiers
Fichiers binaires
234 / 399
Fichiers binaires — introduction
À la différence des fichiers textes qui sont constitués de caractères (et sont donc potentiellement
lisibles par un être humain directement), les fichiers binaires contiennent des suites de bits non
formatées.
235 / 399
Fichiers binaires — introduction
À la différence des fichiers textes qui sont constitués de caractères (et sont donc potentiellement
lisibles par un être humain directement), les fichiers binaires contiennent des suites de bits non
formatées.
Par exemple, si ft pointe vers un fichier texte, l’instruction
fprintf(ft, "%d", 261)
235 / 399
Fichiers binaires — introduction
À la différence des fichiers textes qui sont constitués de caractères (et sont donc potentiellement
lisibles par un être humain directement), les fichiers binaires contiennent des suites de bits non
formatées.
Par exemple, si ft pointe vers un fichier texte, l’instruction
fprintf(ft, "%d", 261)
place les caractères ’2’ , ’6’ et ’1’ dans le fichier en question. Ceci écrit donc les trois octets
00110010 , 00110110 et 00110001 , obtenus en interprétant en base deux les valeurs ASCII de ces
caractères décimaux.
235 / 399
Fichiers binaires — introduction
À la différence des fichiers textes qui sont constitués de caractères (et sont donc potentiellement
lisibles par un être humain directement), les fichiers binaires contiennent des suites de bits non
formatées.
Par exemple, si ft pointe vers un fichier texte, l’instruction
fprintf(ft, "%d", 261)
place les caractères ’2’ , ’6’ et ’1’ dans le fichier en question. Ceci écrit donc les trois octets
00110010 , 00110110 et 00110001 , obtenus en interprétant en base deux les valeurs ASCII de ces
caractères décimaux.
En revanche, si fb pointe vers un fichier binaire, on emploiera l’instruction d’écriture fwrite et,
si v est une variable entière de valeur 261 ,
fwrite(&v, sizeof(int), 1, fb)
écrit dans le fichier les 4 (= sizeof(int) ) octets 00000000 , 00000000 , 00000001 , 00000101 à la suite
(développement en base deux de la valeur).
235 / 399
Fichiers binaires — introduction
À la différence des fichiers textes qui sont constitués de caractères (et sont donc potentiellement
lisibles par un être humain directement), les fichiers binaires contiennent des suites de bits non
formatées.
Par exemple, si ft pointe vers un fichier texte, l’instruction
fprintf(ft, "%d", 261)
place les caractères ’2’ , ’6’ et ’1’ dans le fichier en question. Ceci écrit donc les trois octets
00110010 , 00110110 et 00110001 , obtenus en interprétant en base deux les valeurs ASCII de ces
caractères décimaux.
En revanche, si fb pointe vers un fichier binaire, on emploiera l’instruction d’écriture fwrite et,
si v est une variable entière de valeur 261 ,
fwrite(&v, sizeof(int), 1, fb)
écrit dans le fichier les 4 (= sizeof(int) ) octets 00000000 , 00000000 , 00000001 , 00000101 à la suite
(développement en base deux de la valeur).
Ainsi, on écrit/lit dans/depuis un fichier binaire directement des valeurs, sans se soucier du
format à adopter. 235 / 399
Ouverture de fichiers en mode binaire
On utilise les modes d’ouverture habituels avec un b en plus pour signaler l’ouverture en binaire.
I "rb" : lecture binaire seule.
I "ab" : écriture binaire en ajout. Permet d’écrire dans le fichier en partant de la fin. Si le
fichier n’existe pas, il est créé.
I "rb+" : lecture binaire et écriture binaire .
I "wb+" : lecture binaire et écriture binaire avec suppression préalable du contenu du fichier.
Si le fichier n’existe pas, il est créé.
I "ab+" : lecture binaire et écriture binaire en ajout. Permet de lire et d’écrire dans le fichier
en partant de la fin. Si le fichier n’existe pas, il est créé.
236 / 399
Écriture dans un fichier binaire
On utilise la fonction
int fwrite(void *ptr, int taille, int nb, FILE *f);
237 / 399
Écriture dans un fichier binaire
On utilise la fonction
int fwrite(void *ptr, int taille, int nb, FILE *f);
Cette fonction écrit les nb valeurs de taille taille octets pointées par le pointeur ptr dans le
fichier pointé par f .
237 / 399
Écriture dans un fichier binaire
On utilise la fonction
int fwrite(void *ptr, int taille, int nb, FILE *f);
Cette fonction écrit les nb valeurs de taille taille octets pointées par le pointeur ptr dans le
fichier pointé par f .
237 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
#include <stdio.h>
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
int main() {
FILE *f;
Couleur coul;
coul.rouge = 120; coul.bleu = 200; coul.vert = 12;
return 0;
}
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
#include <stdio.h>
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
int main() {
FILE *f;
Couleur coul;
coul.rouge = 120; coul.bleu = 200; coul.vert = 12;
f = fopen("fic", "wb");
return 0;
}
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
#include <stdio.h>
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
int main() {
FILE *f;
Couleur coul;
coul.rouge = 120; coul.bleu = 200; coul.vert = 12;
f = fopen("fic", "wb");
fwrite(&coul, sizeof(Couleur), 1, f);
return 0;
}
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
#include <stdio.h>
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
int main() {
FILE *f;
Couleur coul;
coul.rouge = 120; coul.bleu = 200; coul.vert = 12;
f = fopen("fic", "wb");
fwrite(&coul, sizeof(Couleur), 1, f);
fclose(f);
return 0;
}
238 / 399
Exemple d’utilisation de fwrite
Ce programme écrit en mode binaire dans le fichier fic la valeur d’une variable d’un type
structuré.
#include <stdio.h>
typedef struct {
unsigned char rouge;
unsigned char bleu;
unsigned char vert;
} Couleur;
int main() {
FILE *f;
Couleur coul;
coul.rouge = 120; coul.bleu = 200; coul.vert = 12;
f = fopen("fic", "wb");
fwrite(&coul, sizeof(Couleur), 1, f);
fclose(f);
return 0;
}
Remarque : il n’a pas été nécessaire ici de définir un format d’écriture (ce qui aurait été imposé
en cas d’écriture dans un fichier texte).
238 / 399
Lecture dans un fichier binaire
On utilise la fonction
int fread(void *ptr, int taille, int nb, FILE *f);
239 / 399
Lecture dans un fichier binaire
On utilise la fonction
int fread(void *ptr, int taille, int nb, FILE *f);
Cette fonction lit nb valeurs de taille taille octets dans le fichier pointé par f et les place à
l’adresse pointée par le pointeur ptr .
239 / 399
Lecture dans un fichier binaire
On utilise la fonction
int fread(void *ptr, int taille, int nb, FILE *f);
Cette fonction lit nb valeurs de taille taille octets dans le fichier pointé par f et les place à
l’adresse pointée par le pointeur ptr .
239 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
fclose(f);
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
fclose(f);
f = fopen("fic", "rb");
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
fclose(f);
f = fopen("fic", "rb");
fread(tab_2, sizeof(int), 12, f);
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
fclose(f);
f = fopen("fic", "rb");
fread(tab_2, sizeof(int), 12, f);
fclose(f);
return 0;
}
240 / 399
Exemple d’utilisation de fread
Ce programme écrit en mode binaire dans le fichier fic un tableau d’entiers et le lit.
#include <stdio.h>
int main() {
FILE *f;
int i, tab_1[12], tab_2[12];
f = fopen("fic", "wb");
fwrite(tab_1, sizeof(int), 12, f);
fclose(f);
f = fopen("fic", "rb");
fread(tab_2, sizeof(int), 12, f);
fclose(f);
return 0;
}
Remarque : il n’a pas été nécessaire de se baser sur un format pour la lecture (ce qui aurait été
imposé en cas de lecture depuis un fichier texte).
240 / 399
Plan
Types
Notion de type
Types scalaires
Types construits
241 / 399
Plan
Types
Notion de type
Types scalaires
Types construits
242 / 399
Types
243 / 399
Types
Dire qu’une variable x est de type T signifie que la valeur de x est dans T .
243 / 399
Types
Dire qu’une variable x est de type T signifie que la valeur de x est dans T .
243 / 399
Types
Dire qu’une variable x est de type T signifie que la valeur de x est dans T .
243 / 399
Type d’une variable
Le type d’une variable indique comment interpréter la zone mémoire qui lui est attribuée ainsi
que sa taille.
244 / 399
Type d’une variable
Le type d’une variable indique comment interpréter la zone mémoire qui lui est attribuée ainsi
que sa taille.
L’opérateur sizeof permet de connaître la taille en octets d’un type. On peut aussi l’appliquer à
une valeur. P.ex., sizeof(int) et sizeof(33) valent 4 .
244 / 399
Type d’une variable
Le type d’une variable indique comment interpréter la zone mémoire qui lui est attribuée ainsi
que sa taille.
L’opérateur sizeof permet de connaître la taille en octets d’un type. On peut aussi l’appliquer à
une valeur. P.ex., sizeof(int) et sizeof(33) valent 4 .
244 / 399
Type d’une variable
Le type d’une variable indique comment interpréter la zone mémoire qui lui est attribuée ainsi
que sa taille.
L’opérateur sizeof permet de connaître la taille en octets d’un type. On peut aussi l’appliquer à
une valeur. P.ex., sizeof(int) et sizeof(33) valent 4 .
Le type d’une variable est attribué à sa déclaration et ne peut pas être modifié.
244 / 399
Plan
Types
Notion de type
Types scalaires
Types construits
245 / 399
Types entier
On se place sur une machine 64 bits.
246 / 399
Types entier
On se place sur une machine 64 bits.
247 / 399
Entiers non signés
Dans le cas où l’on a besoin de représenter uniquement des valeurs entières positives, on utilisera
les versions non signées des types entiers.
Quelques avantages de ce procédé :
1. possibilité de représenter des entiers plus grands ;
2. gain de lisibilité du programme.
247 / 399
Entiers non signés
Dans le cas où l’on a besoin de représenter uniquement des valeurs entières positives, on utilisera
les versions non signées des types entiers.
Quelques avantages de ce procédé :
1. possibilité de représenter des entiers plus grands ;
2. gain de lisibilité du programme.
produisent une boucle infinie. En effet, i étant non signé, il est toujours positif et donc la
condition i >= 0 est toujours vraie.
247 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
248 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
248 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
248 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
248 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
Un entier peut être représenté par un caractère car tout caractère est représenté par son code
ASCII (qui est un entier compris entre 0 et 127 ).
248 / 399
Constantes entières
Il existe plusieurs manières d’exprimer des constantes entières :
I en base dix : 0 , 29 , -322 , ...
Un entier peut être représenté par un caractère car tout caractère est représenté par son code
ASCII (qui est un entier compris entre 0 et 127 ).
Attention : ne pas confondre les caractères chiffres avec les entiers (l’entier ’1’ vaut 49 et non
pas 1 ).
248 / 399
Types flottant
Le fichier d’en-tête float.h contient des constantes donnant d’autres renseignements sur les
types flottant.
249 / 399
Danger des types flottant
250 / 399
Danger des types flottant
250 / 399
Danger des types flottant
250 / 399
Danger des types flottant : une solution partielle
Les types flottant présentent divers désavantages par rapport aux types entier :
1. représentation non exacte des nombres ;
2. opérations arithmétiques beaucoup moins efficaces.
251 / 399
Danger des types flottant : une solution partielle
Les types flottant présentent divers désavantages par rapport aux types entier :
1. représentation non exacte des nombres ;
2. opérations arithmétiques beaucoup moins efficaces.
251 / 399
Danger des types flottant : une solution partielle
Les types flottant présentent divers désavantages par rapport aux types entier :
1. représentation non exacte des nombres ;
2. opérations arithmétiques beaucoup moins efficaces.
Solution partielle : on représente par l’entier x × 10k tout nombre x qui dispose de k > 0
chiffres (en base dix) après la virgule, k étant fixé.
251 / 399
Danger des types flottant : une solution partielle
Les types flottant présentent divers désavantages par rapport aux types entier :
1. représentation non exacte des nombres ;
2. opérations arithmétiques beaucoup moins efficaces.
Solution partielle : on représente par l’entier x × 10k tout nombre x qui dispose de k > 0
chiffres (en base dix) après la virgule, k étant fixé.
P.ex., si l’on a besoin de manipuler des nombres à k := 2 chiffres après la virgule, les nombres
0.15 et 331.9 sont respectivement représentés par les entiers 15 et 33190.
251 / 399
Opérations sur les types scalaires
Les valeurs d’un type scalaire (entier ou flottant) forment un ensemble totalement ordonné :
étant donné deux valeurs, il est toujours possible de les comparer. On utilise pour cela les
opérateurs relationnels
== , != , <= , >= , < , > .
252 / 399
Opérations sur les types scalaires
Les valeurs d’un type scalaire (entier ou flottant) forment un ensemble totalement ordonné :
étant donné deux valeurs, il est toujours possible de les comparer. On utilise pour cela les
opérateurs relationnels
== , != , <= , >= , < , > .
Il est possible de mélanger des comparaisons de valeurs de types entier et de types flottant. Dans
ce cas, les entiers sont convertis implicitement en une valeur de type flottant avant d’effectuer la
comparaison.
252 / 399
Opérations sur les types scalaires
Les valeurs d’un type scalaire (entier ou flottant) forment un ensemble totalement ordonné :
étant donné deux valeurs, il est toujours possible de les comparer. On utilise pour cela les
opérateurs relationnels
== , != , <= , >= , < , > .
Il est possible de mélanger des comparaisons de valeurs de types entier et de types flottant. Dans
ce cas, les entiers sont convertis implicitement en une valeur de type flottant avant d’effectuer la
comparaison.
Sur des variables de type scalaire sont définis les opérateurs arithmétiques
+ , - , * , / , ++ , -- .
252 / 399
Plan
Types
Notion de type
Types scalaires
Types construits
253 / 399
Types structurés
La syntaxe
typedef struct ALIAS {
TYPE_1 CHAMP_1;
TYPE_2 CHAMP_2;
...
} NOM;
permet de déclarer un type structuré NOM , constitué des champs CHAMP_1 , CHAMP_2 , . . .. L’alias
ALIAS est facultatif.
254 / 399
Types structurés
La syntaxe
typedef struct ALIAS {
TYPE_1 CHAMP_1;
TYPE_2 CHAMP_2;
...
} NOM;
permet de déclarer un type structuré NOM , constitué des champs CHAMP_1 , CHAMP_2 , . . .. L’alias
ALIAS est facultatif.
254 / 399
Types structurés
La syntaxe
typedef struct ALIAS {
TYPE_1 CHAMP_1;
TYPE_2 CHAMP_2;
...
} NOM;
permet de déclarer un type structuré NOM , constitué des champs CHAMP_1 , CHAMP_2 , . . .. L’alias
ALIAS est facultatif.
P.ex.,
typedef struct { } Couple;
int x;
int y; déclare un type structuré Couple qui permet de
représenter des couples d’entiers.
254 / 399
Types structurés
Si x est une variable d’un type structuré T contenant le champ ch , on accède à ce champ par
la syntaxe
x.ch
255 / 399
Types structurés
Si x est une variable d’un type structuré T contenant le champ ch , on accède à ce champ par
la syntaxe
x.ch
Si adr_x est une adresse sur une variable de type T , on accède à ce même champ par la syntaxe
adr_x->ch
255 / 399
Types structurés
Si x est une variable d’un type structuré T contenant le champ ch , on accède à ce champ par
la syntaxe
x.ch
Si adr_x est une adresse sur une variable de type T , on accède à ce même champ par la syntaxe
adr_x->ch
255 / 399
Types structurés
Si x est une variable d’un type structuré T contenant le champ ch , on accède à ce champ par
la syntaxe
x.ch
Si adr_x est une adresse sur une variable de type T , on accède à ce même champ par la syntaxe
adr_x->ch
255 / 399
Opérations sur les types structurés
Les opérateurs relationnels ne sont pas définis sur les types structurés.
Il est donc impossible de tester si deux variables d’un même type structuré sont égales au moyen
de l’opérateur == . Il faut tester l’égalité de chacun des champs qui les constituent.
256 / 399
Opérations sur les types structurés
Les opérateurs relationnels ne sont pas définis sur les types structurés.
Il est donc impossible de tester si deux variables d’un même type structuré sont égales au moyen
de l’opérateur == . Il faut tester l’égalité de chacun des champs qui les constituent.
256 / 399
Opérations sur les types structurés
Les opérateurs relationnels ne sont pas définis sur les types structurés.
Il est donc impossible de tester si deux variables d’un même type structuré sont égales au moyen
de l’opérateur == . Il faut tester l’égalité de chacun des champs qui les constituent.
256 / 399
Opérations sur les types structurés
Les opérateurs relationnels ne sont pas définis sur les types structurés.
Il est donc impossible de tester si deux variables d’un même type structuré sont égales au moyen
de l’opérateur == . Il faut tester l’égalité de chacun des champs qui les constituent.
256 / 399
Types énumérés
La syntaxe
typedef enum {
ENU_1,
ENU_2,
...
} NOM;
permet de déclarer un type énuméré NOM , constitué des énumérateurs ENU_1 , ENU_2 , . . ..
(Attention, on utilise des , et non pas des ; .)
257 / 399
Types énumérés
La syntaxe
typedef enum {
ENU_1,
ENU_2,
...
} NOM;
permet de déclarer un type énuméré NOM , constitué des énumérateurs ENU_1 , ENU_2 , . . ..
(Attention, on utilise des , et non pas des ; .)
Une valeur de ce type prend pour valeur exactement un des énumérateurs qui le constituent.
257 / 399
Types énumérés
La syntaxe
typedef enum {
ENU_1,
ENU_2,
...
} NOM;
permet de déclarer un type énuméré NOM , constitué des énumérateurs ENU_1 , ENU_2 , . . ..
(Attention, on utilise des , et non pas des ; .)
Une valeur de ce type prend pour valeur exactement un des énumérateurs qui le constituent.
P.ex.,
typedef enum { est un type qui permet de représenter des
FAUX, booléens.
VRAI Une valeur de type Booleen est soit FAUX , soit
} Booleen;
VRAI .
257 / 399
Types énumérés
typedef enum { Les énumérateurs sont des expressions
LUNDI, /* = 0 */ entières. Leur valeur est déterminée par leur
MARDI, /* = 1 */
MERCREDI, /* = 2 */
ordre de déclaration dans le type.
JEUDI, /* = 3 */ Ces instructions affichent 2 . En effet, LUNDI
VENDREDI, /* = 4 */
SAMEDI, /* = 5 */ vaut 0 car il est le énumérateur déclaré et
1er
DIMANCHE /* = 6 */ les valeurs des suivants s’incrémentent selon
} Jour; leur ordre de déclaration.
...
printf("%d\n", MERCREDI);
258 / 399
Types énumérés
typedef enum { Les énumérateurs sont des expressions
LUNDI, /* = 0 */ entières. Leur valeur est déterminée par leur
MARDI, /* = 1 */
MERCREDI, /* = 2 */
ordre de déclaration dans le type.
JEUDI, /* = 3 */ Ces instructions affichent 2 . En effet, LUNDI
VENDREDI, /* = 4 */
SAMEDI, /* = 5 */ vaut 0 car il est le énumérateur déclaré et
1er
DIMANCHE /* = 6 */ les valeurs des suivants s’incrémentent selon
} Jour; leur ordre de déclaration.
...
printf("%d\n", MERCREDI);
259 / 399
Types énumérés
L’utilisation de branchement switch est particulièrement adaptée pour traiter une variable d’un
type énuméré.
259 / 399
Types énumérés
L’utilisation de branchement switch est particulièrement adaptée pour traiter une variable d’un
type énuméré.
259 / 399
Types énumérés
L’utilisation de branchement switch est particulièrement adaptée pour traiter une variable d’un
type énuméré.
259 / 399
Opérations sur les types énumérés
Contrairement aux types structurés, il est possible de comparer les éléments d’un type énuméré
au moyen des opérateurs relationnels.
Ceci est une conséquence du fait que les énumérateurs sont des entiers.
260 / 399
Opérations sur les types énumérés
Contrairement aux types structurés, il est possible de comparer les éléments d’un type énuméré
au moyen des opérateurs relationnels.
Ceci est une conséquence du fait que les énumérateurs sont des entiers.
260 / 399
Opérations sur les types énumérés
Contrairement aux types structurés, il est possible de comparer les éléments d’un type énuméré
au moyen des opérateurs relationnels.
Ceci est une conséquence du fait que les énumérateurs sont des entiers.
L’opérateur de taille sizeof renvoie 4 sur les valeurs d’un type énuméré. C’est la taille
occupée par le type int .
260 / 399
Plan
Types structurés
Déclaration et initialisation
Affectation et comparaison
Dans les fonctions
Alignement en mémoire
261 / 399
Plan
Types structurés
Déclaration et initialisation
Affectation et comparaison
Dans les fonctions
Alignement en mémoire
262 / 399
Déclaration de types structurés récursifs
Il est possible de déclarer des types structurés récursifs en faisant usage de l’alias et du mot clé
struct :
Ceci fonctionne car la taille d’un pointeur vers une valeur de type T est connue et indépendante
de la nature de T .
Attention, la déclaration
1 typedef struct _Liste {
2 int e;
3 struct _Liste s;
4 } Liste;
264 / 399
Initialisation d’une variable d’un type structuré
Il est possible d’initialiser les champs d’une variable d’un type structuré au moment de sa
déclaration.
On utilise pour cela l’opérateur d’affectation = avec comme valeur droite les valeurs des champs
à affecter dans des accolades et séparées par des virgules.
Par exemple,
265 / 399
Plan
Types structurés
Déclaration et initialisation
Affectation et comparaison
Dans les fonctions
Alignement en mémoire
266 / 399
Affectation de variables d’un type structuré
Considérons le code
267 / 399
Affectation de variables d’un type structuré
Considérons le code
? ? ? ?
v1 : X v2 : X
267 / 399
Affectation de variables d’un type structuré
Considérons le code
? ? ? ?
v1 : X v2 : X
10 3.14 ? ?
v1 : X v2 : X
267 / 399
Affectation de variables d’un type structuré
Considérons le code
? ? ? ?
v1 : X v2 : X
10 3.14 ? ?
v1 : X v2 : X
10 3.14 10 3.14
v1 : X v2 : X
267 / 399
Affectation de variables d’un type structuré
Considérons le code
? ? ? ?
v1 : X v2 : X
10 3.14 ? ?
v1 : X v2 : X
10 3.14 10 3.14
v1 : X v2 : X
10 3.14 20 3.14
v1 : X v2 : X
267 / 399
Affectation de variables d’un type structuré
Considérons le code
? ? ? ?
v1 : X v2 : X
10 3.14 ? ?
v1 : X v2 : X
10 3.14 10 3.14
v1 : X v2 : X
10 3.14 20 3.14
v1 : X v2 : X
Observation : l’affectation recopie les champs d’une variable d’un type scalaire.
267 / 399
Affectation de variables d’un type structuré
Considérons le code
268 / 399
Affectation de variables d’un type structuré
Considérons le code
10 3.14 ? ?
v1 : Y v2 : Y
268 / 399
Affectation de variables d’un type structuré
Considérons le code
10 3.14 ? ?
v1 : Y v2 : Y
10 3.14 10 3.14
a : int f : float {’a’, ’b’, ’c’} a : int f : float {’a’, ’b’, ’c’}
l. 10 : x : X t : char[3] x : X t : char[3]
v1 : Y v2 : Y
268 / 399
Affectation de variables d’un type structuré
Considérons le code
10 3.14 ? ?
v1 : Y v2 : Y
10 3.14 10 3.14
a : int f : float {’a’, ’b’, ’c’} a : int f : float {’a’, ’b’, ’c’}
l. 10 : x : X t : char[3] x : X t : char[3]
v1 : Y v2 : Y
10 3.14 10 1.8
a : int f : float {’a’, ’b’, ’c’} a : int f : float {’g’, ’b’, ’c’}
l. 12 : x : X t : char[3] x : X t : char[3]
v1 : Y v2 : Y
268 / 399
Affectation de variables d’un type structuré
Considérons le code
10 3.14 ? ?
v1 : Y v2 : Y
10 3.14 10 3.14
a : int f : float {’a’, ’b’, ’c’} a : int f : float {’a’, ’b’, ’c’}
l. 10 : x : X t : char[3] x : X t : char[3]
v1 : Y v2 : Y
10 3.14 10 1.8
a : int f : float {’a’, ’b’, ’c’} a : int f : float {’g’, ’b’, ’c’}
l. 12 : x : X t : char[3] x : X t : char[3]
v1 : Y v2 : Y
Observation : l’affectation recopie les champs d’une variable d’un type structuré de manière
récursive et les tableaux statiques. 268 / 399
Affectation de variables d’un type structuré
Considérons le code
269 / 399
Affectation de variables d’un type structuré
Considérons le code
l. 11 : v1.t 3 ? ?
v1 : T v2 : T
269 / 399
Affectation de variables d’un type structuré
Considérons le code
l. 11 : v1.t 3 ? ?
v1 : T v2 : T
l. 12 : v1.t 3 v1.t 3
v1 : T v2 : T
269 / 399
Affectation de variables d’un type structuré
Considérons le code
l. 11 : v1.t 3 ? ?
v1 : T v2 : T
l. 12 : v1.t 3 v1.t 3
v1 : T v2 : T
l. 14 : v1.t 3 v1.t 2
v1 : T v2 : T
269 / 399
Affectation de variables d’un type structuré
Considérons le code
l. 11 : v1.t 3 ? ?
v1 : T v2 : T
l. 12 : v1.t 3 v1.t 3
v1 : T v2 : T
l. 14 : v1.t 3 v1.t 2
v1 : T v2 : T
Observation : l’affectation ne recopie pas les tableaux dynamiques. Seule l’adresse d’un tableau
dynamique est recopiée. C’est une copie de surface. 269 / 399
Affectation de variables d’un type structuré
Règle générale : pour chaque déclaration d’un type structuré X , on définit (dans le même
module) une fonction de prototype
int copier_X(const X *v1, X *v2);
Considérons le code
271 / 399
Comparaison de variables d’un type structuré
Règle générale : pour chaque déclaration d’un type structuré X , on définit (dans le même
module) deux fonctions de prototypes
int sont_ega_X(const X *v1, const X *v2);
Attention : si X est composé d’un champ qui est un type structuré Y , il faut appeler dans
sont_ega_X la fonction de comparaison sont_ega_Y .
272 / 399
Destruction de variables d’un type structuré
Règle générale : pour chaque déclaration d’un type structuré X , on définit (dans le même
module) une fonction de prototype
void detruire_X(X *v);
Attention : si X est composé d’un champ qui est un type structuré Y , il faut appeler dans
detruire_X la fonction de destruction detruire_Y .
273 / 399
Plan
Types structurés
Déclaration et initialisation
Affectation et comparaison
Dans les fonctions
Alignement en mémoire
274 / 399
Renvoi d’une variable d’un type structuré
Le code
est correct ( twist renvoie le couple obtenu par échange des coordonnées de celui passé en
argument).
twist renvoie une variable d’un type structuré.
Cependant, il n’est pas efficace car, à chaque appel de fonction
d = twist(c);
Le code
est correct ( prem_egaux teste si les premières cases des tableaux sont égales).
prem_egaux est paramétrée par une variable d’un type structuré.
Cependant, il n’est pas efficace car à chaque appel de fonction
prem_egaux(y);
276 / 399
Passage par adresse vs passage par valeur
Soit une fonction fct paramétrée par une variable x d’un type structuré T .
277 / 399
Passage par adresse vs passage par valeur
Soit une fonction fct paramétrée par une variable x d’un type structuré T .
Il est d’usage courant d’adopter la convention suivante :
I si les champs de x doivent être modifiés par la fonction, alors on recourt à un passage par
adresse
... fct(T *x, ...) { ... }
277 / 399
Passage par adresse vs passage par valeur
Soit une fonction fct paramétrée par une variable x d’un type structuré T .
Il est d’usage courant d’adopter la convention suivante :
I si les champs de x doivent être modifiés par la fonction, alors on recourt à un passage par
adresse
... fct(T *x, ...) { ... }
I si les champs de x ne doivent pas être modifiés par la fonction, alors on recourt à un
passage par valeur
... fct(T x, ...) { ... }
277 / 399
Passage par adresse vs passage par valeur
Soit une fonction fct paramétrée par une variable x d’un type structuré T .
Il est d’usage courant d’adopter la convention suivante :
I si les champs de x doivent être modifiés par la fonction, alors on recourt à un passage par
adresse
... fct(T *x, ...) { ... }
I si les champs de x ne doivent pas être modifiés par la fonction, alors on recourt à un
passage par valeur
... fct(T x, ...) { ... }
Cette conception est erronée car il est possible de « modifier » une variable d’un type structuré
passée par valeur à une fonction.
277 / 399
Passage par adresse vs passage par valeur
Considérons en effet le code suivant :
provoque la recopie de trois valeurs (ce qui est encore acceptable) mais « modifie » les valeurs
pointées par le champ tab de s , malgré le passage par valeur.
Conclusion : écrire des fonctions avec passage par valeur des paramètres d’un type structuré ne
présente que des désavantages.
278 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
2. tous les paramètres d’un type structuré sont passés par adresse dans une fonction.
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
2. tous les paramètres d’un type structuré sont passés par adresse dans une fonction.
Ainsi, un prototype de fonction habituel est
int fct(T *x, E1 e1, ..., EN en, S1 *s1, ..., SM *sm);
où
I le type de retour est int (renvoi d’un code d’erreur) ;
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
2. tous les paramètres d’un type structuré sont passés par adresse dans une fonction.
Ainsi, un prototype de fonction habituel est
int fct(T *x, E1 e1, ..., EN en, S1 *s1, ..., SM *sm);
où
I le type de retour est int (renvoi d’un code d’erreur) ;
I x est l’adresse d’une variable d’un type structuré T ;
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
2. tous les paramètres d’un type structuré sont passés par adresse dans une fonction.
Ainsi, un prototype de fonction habituel est
int fct(T *x, E1 e1, ..., EN en, S1 *s1, ..., SM *sm);
où
I le type de retour est int (renvoi d’un code d’erreur) ;
I x est l’adresse d’une variable d’un type structuré T ;
I e1, ..., en sont les entrées de la fonction (adresses ou non) ;
279 / 399
Variables d’un type structuré dans les fonctions
En résumé, on adopte les deux règles suivantes :
1. une fonction ne renvoie jamais de valeur d’un type structuré ;
2. tous les paramètres d’un type structuré sont passés par adresse dans une fonction.
Ainsi, un prototype de fonction habituel est
int fct(T *x, E1 e1, ..., EN en, S1 *s1, ..., SM *sm);
où
I le type de retour est int (renvoi d’un code d’erreur) ;
I x est l’adresse d’une variable d’un type structuré T ;
I e1, ..., en sont les entrées de la fonction (adresses ou non) ;
I s1, ..., sm sont les sorties de la fonction (qui sont des adresses).
279 / 399
Variables d’un type structuré dans les fonctions
P.ex., voici le nécessaire pour calculer la somme pondérée de deux points selon les conventions
établies :
1 typdef struct {
2 float x;
3 float y;
4 } Point;
5
6 void somme_points(const Point *p1, const Point *p2,
7 float coeff1, float coeff2,
8 Point *res) {
9
10 assert(p1 != NULL);
11 assert(p2 != NULL);
12 assert(res != NULL);
13
14 res->x = coeff1 * p1->x + coeff2 * p2->x;
15 res->y = coeff1 * p1->y + coeff2 * p2->y;
16 }
280 / 399
Résumé
Voici en résumé la bonne marche à suivre lors de la manipulation de types structurés :
1. on utilise l’alias lors de la déclaration de types structurés récursifs et/ou mutuellement
récursifs ;
2. toute déclaration d’un type structuré s’accompagne de la définition des quatre fonctions
suivantes :
I une fonction de copie ;
I une fonction de test d’égalité ;
I une fonction de test d’inégalité ;
I une fonction de destruction ;
4. on passe les paramètres d’un type structuré par adresse (ne pas oublier d’ajouter les
qualificateurs const nécessaires).
281 / 399
Plan
Types structurés
Déclaration et initialisation
Affectation et comparaison
Dans les fonctions
Alignement en mémoire
282 / 399
Alignement en mémoire
L’alignement en mémoire d’une donnée est la façon dont celle-ci est organisée dans la mémoire.
283 / 399
Alignement en mémoire
L’alignement en mémoire d’une donnée est la façon dont celle-ci est organisée dans la mémoire.
Par exemple, nous avons déjà vu que les tableaux de taille n d’éléments d’un type T sont
organisés en un segment contigu de sizeof(T) * n octets.
283 / 399
Alignement en mémoire
L’alignement en mémoire d’une donnée est la façon dont celle-ci est organisée dans la mémoire.
Par exemple, nous avons déjà vu que les tableaux de taille n d’éléments d’un type T sont
organisés en un segment contigu de sizeof(T) * n octets.
1 octet
... ...
283 / 399
Alignement en mémoire
L’alignement en mémoire d’une donnée est la façon dont celle-ci est organisée dans la mémoire.
Par exemple, nous avons déjà vu que les tableaux de taille n d’éléments d’un type T sont
organisés en un segment contigu de sizeof(T) * n octets.
1 octet
... ...
On peut se poser de la même manière la question de l’alignement mémoire des variables d’un
type structuré.
283 / 399
Alignement en mémoire des variables d’un type structuré
Considérons les déclarations de types
A et B sont des types structurés composés des mêmes champs. Il n’y a que l’ordre de leur
déclaration qui diffère.
Cependant,
1 printf("%lu␣%lu\n", sizeof(A), sizeof(B));
affiche 8 12 .
Le fait que les tailles des variables de type A et B diffèrent est dû à leur alignements en
mémoire respectifs qui ne sont pas les mêmes.
284 / 399
Alignement en mémoire des variables d’un type structuré
Soit a une variable de type A . Cette variable est organisée en mémoire en
1 octet
... ...
285 / 399
Alignement en mémoire des variables d’un type structuré
Soit a une variable de type A . Cette variable est organisée en mémoire en
1 octet
... ...
... ...
285 / 399
Alignement en mémoire des variables d’un type structuré
Soit a une variable de type A . Cette variable est organisée en mémoire en
1 octet
... ...
... ...
Les octets en gris intervenant dans l’alignement mémoire de b sont des octets de complétion.
285 / 399
Octets de complétion
Des octets de complétion sont introduits pour que chaque champ c d’une variable d’un type
structuré commence à une adresse multiple d’un entier dépendant du type de c .
286 / 399
Octets de complétion
Des octets de complétion sont introduits pour que chaque champ c d’une variable d’un type
structuré commence à une adresse multiple d’un entier dépendant du type de c .
Dans notre exemple, en sachant que tout champ de type short (resp. int ) doit commencer à une
adresse multiple de 2 (resp. 4 ), on explique l’alignement en mémoire de b précédent :
1 octet
... ...
adr. mult. de 4
286 / 399
Octets de complétion
Des octets de complétion sont introduits pour que chaque champ c d’une variable d’un type
structuré commence à une adresse multiple d’un entier dépendant du type de c .
Dans notre exemple, en sachant que tout champ de type short (resp. int ) doit commencer à une
adresse multiple de 2 (resp. 4 ), on explique l’alignement en mémoire de b précédent :
1 octet
... ...
adr. mult. de 4
Les derniers octets de complétion sont introduits pour que les tableaux de variables de type B
puissent être représentés en vérifiant cet alignement en mémoire.
286 / 399
Accès manuel aux champs
Soit a une variable de type A initialisée par
1 A a = {1000, 2000, 3000};
... ...
... ...
on obtient l’avertissement
Prog.c:3:9: warning: padding struct to align ‘z’ [-Wpadded]
int z;
^
Prog.c:5:1: warning: padding struct size to alignment boundary [-Wpadded]
} B;
^
289 / 399
Alignement en mémoire — ce qu’il faut retenir
L’alignement mémoire d’une variable d’un type structuré dépend de beaucoup de paramètres,
notamment :
I de l’architecture de la machine exécutant ou ayant compilé le programme ;
290 / 399
Alignement en mémoire — ce qu’il faut retenir
L’alignement mémoire d’une variable d’un type structuré dépend de beaucoup de paramètres,
notamment :
I de l’architecture de la machine exécutant ou ayant compilé le programme ;
I du compilateur ayant compilé le programme.
290 / 399
Alignement en mémoire — ce qu’il faut retenir
L’alignement mémoire d’une variable d’un type structuré dépend de beaucoup de paramètres,
notamment :
I de l’architecture de la machine exécutant ou ayant compilé le programme ;
I du compilateur ayant compilé le programme.
Il est donc important de savoir que le calcul de la taille d’une variable d’un type structuré n’est
pas immédiat et dépend du contexte.
290 / 399
Alignement en mémoire — ce qu’il faut retenir
L’alignement mémoire d’une variable d’un type structuré dépend de beaucoup de paramètres,
notamment :
I de l’architecture de la machine exécutant ou ayant compilé le programme ;
I du compilateur ayant compilé le programme.
Il est donc important de savoir que le calcul de la taille d’une variable d’un type structuré n’est
pas immédiat et dépend du contexte.
Dans la pratique, on ne cherchera pas à déclarer des types structurés qui ne nécessitent pas
d’octet de complétion. Au contraire : il est de loin préférable de déclarer les champs dans un
ordre logique favorisant la relecture et la maintenance.
290 / 399
Axe 3 : utiliser quelques techniques avancées
Opérateurs
Pointeurs de fonction
Génération aléatoire
Mémoïsation
291 / 399
Plan
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
292 / 399
Plan
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
293 / 399
Caractéristiques d’un opérateur
294 / 399
Caractéristiques d’un opérateur
294 / 399
Caractéristiques d’un opérateur
2. sa précédence, qui permet de savoir, dans une expression, dans quel ordre appliquer les
différents opérateurs qui la composent ;
294 / 399
Caractéristiques d’un opérateur
2. sa précédence, qui permet de savoir, dans une expression, dans quel ordre appliquer les
différents opérateurs qui la composent ;
3. pour les opérateurs binaires (d’arité 2), son sens d’associativité, qui permet de savoir, dans
une expression, dans quel sens appliquer des mêmes opérateurs qui la composent.
294 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
Considérons l’expression 4 - 3 - 2 - 1 .
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
Considérons l’expression 4 - 3 - 2 - 1 .
Suivant le sens d’associativité de - , il y a deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
Considérons l’expression 4 - 3 - 2 - 1 .
Suivant le sens d’associativité de - , il y a deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
Considérons l’expression 4 - 3 - 2 - 1 .
Suivant le sens d’associativité de - , il y a deux manières de l’évaluer :
295 / 399
Précédence et associativité des opérateurs
Considérons l’expression 3 * 2 + 1 .
Suivant les priorités relatives des opérateurs * et + , il y deux manières de l’évaluer :
Considérons l’expression 4 - 3 - 2 - 1 .
Suivant le sens d’associativité de - , il y a deux manières de l’évaluer :
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
296 / 399
Opérateurs de gestion de la mémoire
297 / 399
Opérateurs de gestion de la mémoire
297 / 399
Opérateurs de gestion de la mémoire
297 / 399
Opérateurs de gestion de la mémoire
297 / 399
Opérateurs de gestion de la mémoire
297 / 399
Opérateurs de gestion de la mémoire
297 / 399
Plan
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
298 / 399
Opérateurs arithmétiques
299 / 399
Opérateurs arithmétiques
299 / 399
Opérateurs arithmétiques
299 / 399
Opérateurs arithmétiques
299 / 399
Opérateurs arithmétiques
299 / 399
L’opérateur modulo
L’opérateur modulo % calcule le reste de la division euclidienne de son premier opérande par son
second.
300 / 399
L’opérateur modulo
L’opérateur modulo % calcule le reste de la division euclidienne de son premier opérande par son
second.
D’un point de vue mathématique, si a et b sont deux entiers, on a a = b × q + r, où
0 6 r 6 b − 1 et q est un entier. q est le quotient et r est le reste, toujours positif.
300 / 399
L’opérateur modulo
L’opérateur modulo % calcule le reste de la division euclidienne de son premier opérande par son
second.
D’un point de vue mathématique, si a et b sont deux entiers, on a a = b × q + r, où
0 6 r 6 b − 1 et q est un entier. q est le quotient et r est le reste, toujours positif.
Cependant, % peut produire des valeurs négatives, dans le cas où l’un des deux opérandes est
négatif.
300 / 399
L’opérateur modulo
L’opérateur modulo % calcule le reste de la division euclidienne de son premier opérande par son
second.
D’un point de vue mathématique, si a et b sont deux entiers, on a a = b × q + r, où
0 6 r 6 b − 1 et q est un entier. q est le quotient et r est le reste, toujours positif.
Cependant, % peut produire des valeurs négatives, dans le cas où l’un des deux opérandes est
négatif.
300 / 399
Les opérateurs d’incrémentation et de décrémentation
Les opérateurs ++ et -- existent en deux versions, suivant qu’ils soient préfixes ou suffixes :
301 / 399
Les opérateurs d’incrémentation et de décrémentation
Les opérateurs ++ et -- existent en deux versions, suivant qu’ils soient préfixes ou suffixes :
1. a++ , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est
l’ancienne valeur de a ;
301 / 399
Les opérateurs d’incrémentation et de décrémentation
Les opérateurs ++ et -- existent en deux versions, suivant qu’ils soient préfixes ou suffixes :
1. a++ , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est
l’ancienne valeur de a ;
2. ++a , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est la
nouvelle valeur de a .
301 / 399
Les opérateurs d’incrémentation et de décrémentation
Les opérateurs ++ et -- existent en deux versions, suivant qu’ils soient préfixes ou suffixes :
1. a++ , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est
l’ancienne valeur de a ;
2. ++a , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est la
nouvelle valeur de a .
int a = 5, b;
b = 3 + a++;
b vaut 8 et a vaut 6 .
301 / 399
Les opérateurs d’incrémentation et de décrémentation
Les opérateurs ++ et -- existent en deux versions, suivant qu’ils soient préfixes ou suffixes :
1. a++ , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est
l’ancienne valeur de a ;
2. ++a , incrémente (de un) la valeur de la variable a et est une expression dont la valeur est la
nouvelle valeur de a .
int a = 5, b; int a = 5, b;
b = 3 + a++; b = 3 + ++a;
b vaut 8 et a vaut 6 . b vaut 9 et a vaut 6 .
301 / 399
Les opérateurs d’incrémentation et de décrémentation
302 / 399
Les opérateurs d’incrémentation et de décrémentation
ne sont pas évaluables (l’effet produit par les lignes 2 dépend du compilateur et de ses options).
302 / 399
Les opérateurs d’incrémentation et de décrémentation
ne sont pas évaluables (l’effet produit par les lignes 2 dépend du compilateur et de ses options).
Règle : pour éviter ce type de piège, on s’interdit de réaliser plus d’une modification d’une même
variable dans une même expression.
302 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
303 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
< , > comparaison stricte 2 −→ deux val. numériques
303 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
< , > comparaison stricte 2 −→ deux val. numériques
<= , >= comparaison large 2 −→ deux val. numériques
303 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
< , > comparaison stricte 2 −→ deux val. numériques
<= , >= comparaison large 2 −→ deux val. numériques
== égalité 2 −→ deux val. numériques
303 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
< , > comparaison stricte 2 −→ deux val. numériques
<= , >= comparaison large 2 −→ deux val. numériques
== égalité 2 −→ deux val. numériques
!= différence 2 −→ deux val. numériques
303 / 399
Opérateurs relationnels
Op. Rôle Ari. Assoc. Opérandes
< , > comparaison stricte 2 −→ deux val. numériques
<= , >= comparaison large 2 −→ deux val. numériques
== égalité 2 −→ deux val. numériques
!= différence 2 −→ deux val. numériques
303 / 399
Opérateurs relationnels
Un pointeur étant une adresse, et donc une valeur numérique, il est possible de comparer deux
pointeurs.
304 / 399
Opérateurs relationnels
Un pointeur étant une adresse, et donc une valeur numérique, il est possible de comparer deux
pointeurs.
304 / 399
Opérateurs relationnels
Un pointeur étant une adresse, et donc une valeur numérique, il est possible de comparer deux
pointeurs.
304 / 399
Opérateurs logiques
305 / 399
Opérateurs logiques
305 / 399
Opérateurs logiques
305 / 399
Opérateurs logiques
305 / 399
Opérateurs logiques
Toutes les expressions formées d’opérateurs logiques produisent une valeur, 0 ou bien 1 .
Cette valeur est
I 1 si l’expression logique est vraie ;
I 0 sinon.
305 / 399
Opérateurs bit à bit
306 / 399
Opérateurs bit à bit
306 / 399
Opérateurs bit à bit
306 / 399
Opérateurs bit à bit
306 / 399
Opérateurs bit à bit
306 / 399
Opérateurs bit à bit
306 / 399
Et/ou/xor/non bit à bit
x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0
x & y 0 0 1 0 0 1 0 0
307 / 399
Et/ou/xor/non bit à bit
x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0
x & y 0 0 1 0 0 1 0 0
x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0
x | y 1 1 1 1 1 1 0 1
307 / 399
Et/ou/xor/non bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0 y 1 0 1 0 1 1 0 0
x & y 0 0 1 0 0 1 0 0 x ^ y 1 1 0 1 1 0 0 1
x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0
x | y 1 1 1 1 1 1 0 1
307 / 399
Et/ou/xor/non bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0 y 1 0 1 0 1 1 0 0
x & y 0 0 1 0 0 1 0 0 x ^ y 1 1 0 1 1 0 0 1
x 0 1 1 1 0 1 0 1
y 1 0 1 0 1 1 0 0 x 0 1 1 1 0 1 0 1
x | y 1 1 1 1 1 1 0 1 ∼x 1 0 0 0 1 0 1 0
307 / 399
Et/ou/xor/non bit à bit
Si les deux opérandes n’ont pas la même taille (en nombre de bits), le plus petit est complété à
gauche par des
I zéros s’il est non signé ou bien positif ;
I uns s’il est négatif et signé.
308 / 399
Et/ou/xor/non bit à bit
Si les deux opérandes n’ont pas la même taille (en nombre de bits), le plus petit est complété à
gauche par des
I zéros s’il est non signé ou bien positif ;
I uns s’il est négatif et signé.
Le signe d’un entier signé est lu sur son bit de poids fort :
I 0 s’il est positif ;
I 1 s’il est négatif.
308 / 399
Et/ou/xor/non bit à bit
Si les deux opérandes n’ont pas la même taille (en nombre de bits), le plus petit est complété à
gauche par des
I zéros s’il est non signé ou bien positif ;
I uns s’il est négatif et signé.
Le signe d’un entier signé est lu sur son bit de poids fort :
I 0 s’il est positif ;
I 1 s’il est négatif.
short x = 5; x 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1
char y = 10; y 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0
x = x | y; x | y 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
308 / 399
Et/ou/xor/non bit à bit
Si les deux opérandes n’ont pas la même taille (en nombre de bits), le plus petit est complété à
gauche par des
I zéros s’il est non signé ou bien positif ;
I uns s’il est négatif et signé.
Le signe d’un entier signé est lu sur son bit de poids fort :
I 0 s’il est positif ;
I 1 s’il est négatif.
short x = 5; x 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1
char y = 10; y 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0
x = x | y; x | y 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1
short x = 5; x 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1
char y = -10; y 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0
x = x | y; x | y 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1
308 / 399
Décalage bit à bit
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 1 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0
309 / 399
Décalage bit à bit
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 0 1 1 1 0 1 0 1 x 0 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 0 0 0 0 1 1 1 0
x 1 1 1 1 0 1 0 1 x 1 1 1 1 0 1 0 1
x << 3 1 0 1 0 1 0 0 0 x >> 3 1 1 1 1 1 1 1 0
309 / 399
Exemple — ensembles finis
Les opérateurs bit à bit sont adaptés pour représenter des ensembles finis et réaliser des
opérations ensemblistes de manière simple et efficace.
310 / 399
Exemple — ensembles finis
Les opérateurs bit à bit sont adaptés pour représenter des ensembles finis et réaliser des
opérations ensemblistes de manière simple et efficace.
310 / 399
Exemple — ensembles finis
Les opérateurs bit à bit sont adaptés pour représenter des ensembles finis et réaliser des
opérations ensemblistes de manière simple et efficace.
Cette indexation permet de représenter tout sous-ensemble S de E par un mot de 32 bit dont le
ie bit code la présence ( 1 ) ou l’absence ( 0 ) de ei dans S.
310 / 399
Exemple — ensembles finis
Les opérateurs bit à bit sont adaptés pour représenter des ensembles finis et réaliser des
opérations ensemblistes de manière simple et efficace.
Cette indexation permet de représenter tout sous-ensemble S de E par un mot de 32 bit dont le
ie bit code la présence ( 1 ) ou l’absence ( 0 ) de ei dans S.
P.ex., l’entier dont l’écriture binaire est
00010000100000000000001100000101
310 / 399
Exemple — ensembles finis
Pour tester si ei appartient à S, il suffit de réaliser un et bit à bit entre l’entier représentant S et
le mot binaire constitué d’un unique 1 en ie position.
Cette expression vaut 0 si ei ∈
/ S et une valeur non nulle sinon.
Ainsi,
1 int appartient_e_i(SousEnsemble s, int i) {
2 assert(0 <= i);
3 assert(i <= 31);
4
5 return (1 << i) & s;
6 }
311 / 399
Exemple — ensembles finis
312 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
return res;
}
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
x = x > > 1;
}
return res;
}
313 / 399
Exemple — Compter le nombre de bits à un
But : écrire une fonction qui renvoie le nombre de bits à un de son paramètre.
On travaille sur des variables de 64 bits. On considère pour cela le type alias Mot64 défini par
typedef unsigned long long Mot64;
313 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
314 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
Ainsi, l’instruction
x = x ^ (x & -x);
314 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
Ainsi, l’instruction
x = x ^ (x & -x);
314 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
Ainsi, l’instruction
x = x ^ (x & -x);
}
314 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
Ainsi, l’instruction
x = x ^ (x & -x);
return res;
}
314 / 399
Exemple — Compter le nombre de bits à un
2e méthode : on constate que pour tout entier x non nul (avec au moins un bit à un),
l’expression x & -x est l’entier qui contient un unique bit à un, le plus à droite de x .
Ainsi, l’instruction
x = x ^ (x & -x);
315 / 399
Exemple — Compter le nombre de bits à un
315 / 399
Exemple — Compter le nombre de bits à un
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 256 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
315 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
res += nombre_un[0xFF & (x > > 24)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
res += nombre_un[0xFF & (x > > 24)];
res += nombre_un[0xFF & (x > > 32)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
res += nombre_un[0xFF & (x > > 24)];
res += nombre_un[0xFF & (x > > 32)];
res += nombre_un[0xFF & (x > > 40)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
res += nombre_un[0xFF & (x > > 24)];
res += nombre_un[0xFF & (x > > 32)];
res += nombre_un[0xFF & (x > > 40)];
res += nombre_un[0xFF & (x > > 48)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
On isole l’octet d’indice i d’une variable x de type Mot64 par l’expression
Comme le nombre de bits à un de x est la somme du nombre de bits à un de chacun des huit
octets qui le constituent, on obtient
int compter_un_3(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFF & x];
res += nombre_un[0xFF & (x > > 8)];
res += nombre_un[0xFF & (x > > 16)];
res += nombre_un[0xFF & (x > > 24)];
res += nombre_un[0xFF & (x > > 32)];
res += nombre_un[0xFF & (x > > 40)];
res += nombre_un[0xFF & (x > > 48)];
res += nombre_un[0xFF & (x > > 56)];
return res;
}
316 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
317 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
int nombre_un[65536]; /* 2^16 */
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 65536 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
317 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
int nombre_un[65536]; /* 2^16 */
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 65536 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
216 × sizeof(int) o
317 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
int nombre_un[65536]; /* 2^16 */
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 65536 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
317 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
int nombre_un[65536]; /* 2^16 */
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 65536 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
218
216 × sizeof(int) o = 218 o = Kio
210
317 / 399
Exemple — Compter le nombre de bits à un
4e méthode : on peut pousser plus loin l’idée précédente en considérant des blocs de deux octets
(au lieu d’un seul).
int nombre_un[65536]; /* 2^16 */
void initialiser_nombre_un() {
int i;
Mot64 x;
for (i = 0 ; i < 65536 ; ++i) {
x = (Mot64) i;
nombre_un[i] = compter_un_2(x);
}
}
218
216 × sizeof(int) o = 218 o = Kio = 256 Kio.
210
317 / 399
Exemple — Compter le nombre de bits à un
Ceci fournit la solution suivante.
int compter_un_4(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFFFF & x];
res += nombre_un[0xFFFF & (x > > 16)];
res += nombre_un[0xFFFF & (x > > 32)];
res += nombre_un[0xFFFF & (x > > 48)];
return res;
}
318 / 399
Exemple — Compter le nombre de bits à un
Ceci fournit la solution suivante.
int compter_un_4(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFFFF & x];
res += nombre_un[0xFFFF & (x > > 16)];
res += nombre_un[0xFFFF & (x > > 32)];
res += nombre_un[0xFFFF & (x > > 48)];
return res;
}
Elle ne demande que quatre lectures dans le tableau nombre_un , au lieu des huit de la méthode
précédente.
318 / 399
Exemple — Compter le nombre de bits à un
Ceci fournit la solution suivante.
int compter_un_4(Mot64 x) {
int res;
res = 0;
res += nombre_un[0xFFFF & x];
res += nombre_un[0xFFFF & (x > > 16)];
res += nombre_un[0xFFFF & (x > > 32)];
res += nombre_un[0xFFFF & (x > > 48)];
return res;
}
Elle ne demande que quatre lectures dans le tableau nombre_un , au lieu des huit de la méthode
précédente.
À l’extrême, il est impossible de maintenir un tableau nombre_un qui ne demanderait qu’une seule
lecture. En effet, sa taille mémoire serait de
266
264 × sizeof(int) o = 266 o = Gio = 236 Gio.
230
318 / 399
Exemple — Compter le nombre de bits à un
5e méthode : cette méthode est la plus compliquée. Elle se base sur une compréhension très fine
du fonctionnement des opérateurs bit à bit.
319 / 399
Exemple — Compter le nombre de bits à un
5e méthode : cette méthode est la plus compliquée. Elle se base sur une compréhension très fine
du fonctionnement des opérateurs bit à bit.
int compter_un_5(Mot64 x) {
x = x - ((x > > 1) & 0x5555555555555555LLU);
x = (x & 0x3333333333333333LLU) +
((x > > 2) & 0x3333333333333333LLU);
x = (x + (x > > 4)) & 0x0F0F0F0F0F0F0F0FLLU;
x = (x * 0x0101010101010101LLU) > > 56;
return (int) x;
}
319 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
...
/* Instructions */
...
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
...
/* Instructions */
...
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
Mesurons maintenant les efficacités des cinq méthodes.
On utilise pour cela la fonction
clock_t clock(void);
de time.h .
320 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
Mot64 mot64_alea() {
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
Mot64 mot64_alea() {
Mot64 gauche, droite;
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
Mot64 mot64_alea() {
Mot64 gauche, droite;
droite = (Mot64) rand();
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
Mot64 mot64_alea() {
Mot64 gauche, droite;
droite = (Mot64) rand();
gauche = ((Mot64) rand()) < < 32;
321 / 399
Mesure du temps d’exécution
On mesure le temps d’exécution des cinq méthodes en leur donnant en entrée des nombres de 64
bits générés de manière aléatoire.
Pour générer un nombre de 32 bits de manière aléatoire, on utilise la fonction
int rand(void);
de stdlib.h .
Le nombre de 64 bits est construit en générant aléatoirement ses quatre octets de droite, puis ses
quatre octets de gauche et en les associant avec un ou bit à bit :
Mot64 mot64_alea() {
Mot64 gauche, droite;
droite = (Mot64) rand();
gauche = ((Mot64) rand()) < < 32;
return gauche | droite;
}
321 / 399
Mesure du temps d’exécution
Voici les temps réalisés par chacune des cinq méthodes sur 84000000 nombres de 64 bits
aléatoires :
Méthode Caractéristique Temps (s)
1 Décalage droite 32.9
2 Suppression 1 droite 9.38
3 Tableau 256 2.23
4 Tableau 65536 1.93
5 Compliquée 1.82
322 / 399
Mesure du temps d’exécution
Voici les temps réalisés par chacune des cinq méthodes sur 84000000 nombres de 64 bits
aléatoires :
Méthode Caractéristique Temps (s)
1 Décalage droite 32.9
2 Suppression 1 droite 9.38
3 Tableau 256 2.23
4 Tableau 65536 1.93
5 Compliquée 1.82
La 4e méthode est plus de 16 fois plus rapide que la 1re. Elle demande en revanche (tout comme la
3e) un pré-calcul et une occupation mémoire (par le tableau nombre_un ).
322 / 399
Mesure du temps d’exécution
Voici les temps réalisés par chacune des cinq méthodes sur 84000000 nombres de 64 bits
aléatoires :
Méthode Caractéristique Temps (s)
1 Décalage droite 32.9
2 Suppression 1 droite 9.38
3 Tableau 256 2.23
4 Tableau 65536 1.93
5 Compliquée 1.82
La 4e méthode est plus de 16 fois plus rapide que la 1re. Elle demande en revanche (tout comme la
3e) un pré-calcul et une occupation mémoire (par le tableau nombre_un ).
La 5e méthode est la plus rapide et ne demande aucun pré-calcul. Elle est en revanche difficile à
comprendre et très difficile à imaginer.
322 / 399
Plan
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
323 / 399
Opérateurs d’affectation
324 / 399
Opérateurs d’affectation
324 / 399
Opérateurs d’affectation
324 / 399
Opérateurs d’affectation
324 / 399
Opérateurs d’affectation
324 / 399
Opérateurs d’affectation
Toutes les expressions d’affectation produisent une valeur qui est la valeur qui vient d’être
affectée.
325 / 399
Opérateurs d’affectation
Toutes les expressions d’affectation produisent une valeur qui est la valeur qui vient d’être
affectée.
Par exemple, dans
int a, b;
a = 2;
b = 5;
a *= b += 3;
325 / 399
Plan
Opérateurs
Généralités
Opérateurs d’accès
Opérateurs de calcul
Opérateurs d’affectation
Autres opérateurs
326 / 399
Autres opérateurs
327 / 399
Autres opérateurs
327 / 399
Autres opérateurs
327 / 399
Autres opérateurs
327 / 399
Autres opérateurs
327 / 399
L’opérateur de séquence
Dans l’expression V1, V2 , où V1 et V2 sont des valeurs, on commence par évaluer V1 puis
ensuite V2 . Cette expression produit la valeur V1 .
Variante : avec des parenthèses, dans (V1, V2) , l’évaluation se fait aussi de la gauche vers la
droite mais la valeur de l’expression est V2 .
328 / 399
L’opérateur de séquence
Dans l’expression V1, V2 , où V1 et V2 sont des valeurs, on commence par évaluer V1 puis
ensuite V2 . Cette expression produit la valeur V1 .
Variante : avec des parenthèses, dans (V1, V2) , l’évaluation se fait aussi de la gauche vers la
droite mais la valeur de l’expression est V2 .
L’opérateur , est le plus souvent utilisé dans les champs des boucles for .
328 / 399
L’opérateur de séquence
Dans l’expression V1, V2 , où V1 et V2 sont des valeurs, on commence par évaluer V1 puis
ensuite V2 . Cette expression produit la valeur V1 .
Variante : avec des parenthèses, dans (V1, V2) , l’évaluation se fait aussi de la gauche vers la
droite mais la valeur de l’expression est V2 .
L’opérateur , est le plus souvent utilisé dans les champs des boucles for .
P.ex.,
int i, j, l;
...
for (i = 0, j = l - 1 ; i < j ; ++i, - -j) {
...
}
permet d’obtenir une boucle for avec deux compteurs : i croît et j décroît dans l’intervalle
allant de 0 à l - 1 .
328 / 399
Plan
Pointeurs de fonction
Principe
En paramètre et en retour
Généricité
Implantation de monoïdes
329 / 399
Plan
Pointeurs de fonction
Principe
En paramètre et en retour
Généricité
Implantation de monoïdes
330 / 399
Pointeurs de fonction
En C, on peut manipuler divers types d’objets : des variables d’un type scalaire, des tableaux, des
variables d’un type structuré, des adresses, etc.
331 / 399
Pointeurs de fonction
En C, on peut manipuler divers types d’objets : des variables d’un type scalaire, des tableaux, des
variables d’un type structuré, des adresses, etc.
À l’inverse, les fonctions n’entrent pas dans cette catégorie d’objets directement manipulables.
331 / 399
Pointeurs de fonction
En C, on peut manipuler divers types d’objets : des variables d’un type scalaire, des tableaux, des
variables d’un type structuré, des adresses, etc.
À l’inverse, les fonctions n’entrent pas dans cette catégorie d’objets directement manipulables.
Cependant, au même titre qu’une variable, toute fonction possède une adresse en mémoire. Il
devient alors possible de réaliser des opérations sur les fonctions au moyen de leur adresse.
331 / 399
Pointeurs de fonction
En C, on peut manipuler divers types d’objets : des variables d’un type scalaire, des tableaux, des
variables d’un type structuré, des adresses, etc.
À l’inverse, les fonctions n’entrent pas dans cette catégorie d’objets directement manipulables.
Cependant, au même titre qu’une variable, toute fonction possède une adresse en mémoire. Il
devient alors possible de réaliser des opérations sur les fonctions au moyen de leur adresse.
331 / 399
Adresse d’une fonction
Si fct est une fonction, la syntaxe
&fct
332 / 399
Adresse d’une fonction
Si fct est une fonction, la syntaxe
&fct
#include <stdio.h> }
int somme(int a, int b) {
return a + b; Ce programme affiche 0x40052d et 0x400541 ,
} respectivement les adresses des fonctions
int produit(int a, int b) { somme et produit .
return a * b;
}
int main() {
printf("%p\n", &somme);
printf("%p\n", &produit);
return 0;
332 / 399
Adresse d’une fonction
Si fct est une fonction, la syntaxe
&fct
#include <stdio.h> }
int somme(int a, int b) {
return a + b; Ce programme affiche 0x40052d et 0x400541 ,
} respectivement les adresses des fonctions
int produit(int a, int b) { somme et produit .
return a * b;
}
int main() { Note : aux lignes 9 et 10, il est possible de ne
printf("%p\n", &somme); pas mentionner les & . Le compilateur
printf("%p\n", &produit); comprend implicitement qu’il s’agit de
return 0; pointeurs de fonction.
332 / 399
Le type pointeur de fonction
La syntaxe
T (*FCT)(T1, ..., TN);
où
I T , T1 , . . ., TN sont des types ;
I FCT est un identificateur ;
permet de déclarer un pointeur de fonction.
333 / 399
Le type pointeur de fonction
La syntaxe
T (*FCT)(T1, ..., TN);
où
I T , T1 , . . ., TN sont des types ;
I FCT est un identificateur ;
permet de déclarer un pointeur de fonction.
Celui-ci a FCT pour identificateur et peut être l’adresse d’une fonction de type de retour T et de
signature (T1, ..., TN) .
333 / 399
Le type pointeur de fonction
La syntaxe
T (*FCT)(T1, ..., TN);
où
I T , T1 , . . ., TN sont des types ;
I FCT est un identificateur ;
permet de déclarer un pointeur de fonction.
Celui-ci a FCT pour identificateur et peut être l’adresse d’une fonction de type de retour T et de
signature (T1, ..., TN) .
334 / 399
Champs pointeurs de fonction
Un champ d’un type structuré peut être un pointeur sur une fonction.
Ceci déclare un type structuré sensé modéliser des suites d’entiers :
typedef struct {
int t;
int (*t_suiv)(int);
} Suite;
t contient le terme courant de la suite et t_suiv est la fonction qui, étant donné un terme en
entrée, calcule le terme suivant.
334 / 399
Champs pointeurs de fonction
Un champ d’un type structuré peut être un pointeur sur une fonction.
Ceci déclare un type structuré sensé modéliser des suites d’entiers :
typedef struct {
int t;
int (*t_suiv)(int);
} Suite;
t contient le terme courant de la suite et t_suiv est la fonction qui, étant donné un terme en
entrée, calcule le terme suivant.
Ceci affiche 1 2 4 8 16 32 .
334 / 399
Tableaux de pointeurs de fonction
335 / 399
Tableaux de pointeurs de fonction
335 / 399
Tableaux de pointeurs de fonction
335 / 399
Tableaux de pointeurs de fonction
existe mais rend le code plus difficile à lire. Elle déclare un tableau tab de pointeurs de fonction
sans la déclaration de type préalable de la 1re méthode.
335 / 399
Tableaux de pointeurs de fonction
336 / 399
Tableaux de pointeurs de fonction
Les tableaux de pointeurs de fonction peuvent être dynamiques. La ligne 10 peut être remplacée
par
opb *tab;
tab = (opb *) malloc(sizeof(opb) * 2);
336 / 399
Plan
Pointeurs de fonction
Principe
En paramètre et en retour
Généricité
Implantation de monoïdes
337 / 399
Pointeur de fonction en paramètre
Une fonction peut être paramétrée par un pointeur de fonction. Un paramètre pointeur de
fonction est spécifié avec la même syntaxe que celle qui sert à le déclarer.
338 / 399
Pointeur de fonction en paramètre
Une fonction peut être paramétrée par un pointeur de fonction. Un paramètre pointeur de
fonction est spécifié avec la même syntaxe que celle qui sert à le déclarer.
P.ex.,
int appliquer(int n, int k, int (*f)(int)) {
int i;
for (i = 0 ; i < k ; ++i)
n = f(n);
return n;
}
est une fonction est paramétrée par un pointeur de fonction acceptant un entier et renvoyant un
entier.
338 / 399
Pointeur de fonction en paramètre
int add_1(int n) {
return n + 1; ...
} printf("%d\n",
appliquer(3, 4, &add_1));
int mul_2(int n) {
return 2 * n; printf("%d\n",
} appliquer(3, 4, &mul_2));
339 / 399
Pointeur de fonction en paramètre
int add_1(int n) {
return n + 1; ...
} printf("%d\n",
appliquer(3, 4, &add_1));
int mul_2(int n) {
return 2 * n; printf("%d\n",
} appliquer(3, 4, &mul_2));
(((3 + 1) + 1) + 1) + 1
et affiche donc 7 .
339 / 399
Pointeur de fonction en paramètre
int add_1(int n) {
return n + 1; ...
} printf("%d\n",
appliquer(3, 4, &add_1));
int mul_2(int n) {
return 2 * n; printf("%d\n",
} appliquer(3, 4, &mul_2));
(((3 + 1) + 1) + 1) + 1
et affiche donc 7 .
(((3 * 2) * 2) * 2) * 2
et affiche donc 48 .
339 / 399
Renvoi d’un pointeur de fonction
Il est possible de définir des fonctions dont le type de retour est un pointeur de fonction.
340 / 399
Renvoi d’un pointeur de fonction
Il est possible de définir des fonctions dont le type de retour est un pointeur de fonction.
Pour cela, on procède en deux étapes :
1. on déclare un type alias R pour le pointeur de fonction que l’on souhaite renvoyer ;
340 / 399
Renvoi d’un pointeur de fonction
Il est possible de définir des fonctions dont le type de retour est un pointeur de fonction.
Pour cela, on procède en deux étapes :
1. on déclare un type alias R pour le pointeur de fonction que l’on souhaite renvoyer ;
340 / 399
Renvoi d’un pointeur de fonction
Il est possible de définir des fonctions dont le type de retour est un pointeur de fonction.
Pour cela, on procède en deux étapes :
1. on déclare un type alias R pour le pointeur de fonction que l’on souhaite renvoyer ;
340 / 399
Renvoi d’un pointeur de fonction
Exemple : opération aléatoire sur des entiers.
341 / 399
Renvoi d’un pointeur de fonction
Exemple : opération aléatoire sur des entiers.
Pointeurs de fonction
Principe
En paramètre et en retour
Généricité
Implantation de monoïdes
342 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
I une fonction qui affiche plusieurs fois une même valeur.
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
I une fonction qui affiche plusieurs fois une même valeur.
Une structure de donnée est dite générique si elle peut représenter des données dont le type
n’est pas fixé.
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
I une fonction qui affiche plusieurs fois une même valeur.
Une structure de donnée est dite générique si elle peut représenter des données dont le type
n’est pas fixé.
Exemples :
I une liste dont les éléments sont d’un type non fixé ;
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
I une fonction qui affiche plusieurs fois une même valeur.
Une structure de donnée est dite générique si elle peut représenter des données dont le type
n’est pas fixé.
Exemples :
I une liste dont les éléments sont d’un type non fixé ;
I un arbre binaire dont les éléments sont d’un type non fixé ;
343 / 399
Principe de généricité
Une fonction est dite générique si elle peut accepter des arguments qui ne sont pas seulement
ceux d’un type bien précis.
Exemples :
I une fonction qui teste si deux valeurs sont égales ;
I une fonction qui affiche plusieurs fois une même valeur.
Une structure de donnée est dite générique si elle peut représenter des données dont le type
n’est pas fixé.
Exemples :
I une liste dont les éléments sont d’un type non fixé ;
I un arbre binaire dont les éléments sont d’un type non fixé ;
I un tableau dont les éléments sont d’un type non fixé.
343 / 399
Le type void *
Pour manipuler une donnée dont le type n’est pas spécifié à l’avance, on utilise son adresse.
344 / 399
Le type void *
Pour manipuler une donnée dont le type n’est pas spécifié à l’avance, on utilise son adresse.
Il s’agit donc d’une adresse dont on ne connaît pas le type : c’est une adresse de type void * .
344 / 399
Le type void *
Pour manipuler une donnée dont le type n’est pas spécifié à l’avance, on utilise son adresse.
Il s’agit donc d’une adresse dont on ne connaît pas le type : c’est une adresse de type void * .
344 / 399
Le type void *
Pour manipuler une donnée dont le type n’est pas spécifié à l’avance, on utilise son adresse.
Il s’agit donc d’une adresse dont on ne connaît pas le type : c’est une adresse de type void * .
Pour convertir un pointeur générique ptr_g vers un pointeur d’un type connu T , on utilise
l’opérateur de coercition
(T *) ptr_g .
344 / 399
Le type void *
Pour manipuler une donnée dont le type n’est pas spécifié à l’avance, on utilise son adresse.
Il s’agit donc d’une adresse dont on ne connaît pas le type : c’est une adresse de type void * .
Pour convertir un pointeur générique ptr_g vers un pointeur d’un type connu T , on utilise
l’opérateur de coercition
(T *) ptr_g .
Avant de pouvoir interpréter (c.-à-d. déréférencer) la valeur située à une adresse spécifiée par un
pointeur générique, le convertir est primordial.
344 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
xc = (char *) x;
yc = (char *) y;
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
xc = (char *) x;
yc = (char *) y;
for (i = 0 ; i < nbo ; ++i)
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
xc = (char *) x;
yc = (char *) y;
for (i = 0 ; i < nbo ; ++i)
if (xc[i] != yc[i])
return 0;
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
xc = (char *) x;
yc = (char *) y;
for (i = 0 ; i < nbo ; ++i)
if (xc[i] != yc[i])
return 0;
return 1;
}
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
345 / 399
Fonction générique de comparaison
int ega(int nbo, void *x, void *y) {
char *xc, *yc;
int i;
xc = (char *) x;
yc = (char *) y;
for (i = 0 ; i < nbo ; ++i)
if (xc[i] != yc[i])
return 0;
return 1;
}
La fonction ega est générique : elle permet de tester l’égalité entre deux variables dont le type
n’est pas connu lors de l’écriture de la fonction.
On l’utilise de la manière suivante :
ega(sizeof(T), &t1, &t2)
La fonction aff_tab est générique : elle permet d’afficher les éléments d’un tableau dont le type
n’est pas connu lors de l’écriture de la fonction.
346 / 399
Fonction générique d’affichage de tableau
La fonction aff_tab est générique : elle permet d’afficher les éléments d’un tableau dont le type
n’est pas connu lors de l’écriture de la fonction.
346 / 399
Fonction générique d’affichage de tableau
}
}
La fonction aff_tab est générique : elle permet d’afficher les éléments d’un tableau dont le type
n’est pas connu lors de l’écriture de la fonction.
346 / 399
Fonction générique d’affichage de tableau
}
}
La fonction aff_tab est générique : elle permet d’afficher les éléments d’un tableau dont le type
n’est pas connu lors de l’écriture de la fonction.
346 / 399
Fonction générique d’affichage de tableau
La fonction aff_tab est générique : elle permet d’afficher les éléments d’un tableau dont le type
n’est pas connu lors de l’écriture de la fonction.
346 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
/* Version raccourcie. */
void aff_int(void *x) {
printf("%d", *((int *) x));
}
347 / 399
Fonction générique d’affichage de tableau
On l’utilise la fonction aff_tab de la manière suivante.
/* Version raccourcie. */
void aff_int(void *x) {
printf("%d", *((int *) x));
}
...
aff_tab((void **) tab, 13, &aff_int);
347 / 399
Fonction générique d’affichage de tableau
Pour afficher un tableau tab de taille 23 de pointeurs sur des variables de type structuré Date :
typedef struct {
int jour;
int mois;
int annee;
} Date;
...
void aff_date(void *d) {
Date dd;
dd = *((Date *) d);
printf("%d-%d-%d", dd.jour, dd.mois, dd.annee);
}
...
aff_tab((void **) tab, 23, &aff_date);
348 / 399
Listes génériques
On souhaite définir une structure de donnée liste dont les types des éléments ne sont pas fixés.
349 / 399
Listes génériques
On souhaite définir une structure de donnée liste dont les types des éléments ne sont pas fixés.
Pour cela, on utilise un pointeur générique pour le champ qui contient l’élément de chaque
cellule :
typedef struct _Cellule {
struct _Cellule *suiv;
void *e;
} Cellule;
349 / 399
Listes génériques
On souhaite définir une structure de donnée liste dont les types des éléments ne sont pas fixés.
Pour cela, on utilise un pointeur générique pour le champ qui contient l’élément de chaque
cellule :
typedef struct _Cellule {
struct _Cellule *suiv;
void *e;
} Cellule;
349 / 399
Listes génériques
On souhaite définir une structure de donnée liste dont les types des éléments ne sont pas fixés.
Pour cela, on utilise un pointeur générique pour le champ qui contient l’élément de chaque
cellule :
typedef struct _Cellule {
struct _Cellule *suiv;
void *e;
} Cellule;
349 / 399
Listes génériques
La fonction
void aff_lst(Liste lst, void (*aff_elt)(void *)) {
Cellule *x;
assert(lst != NULL);
assert(aff_elt != NULL);
est une fonction générique pour l’affichage des éléments d’une liste générique.
350 / 399
Listes génériques
On l’utilise de la manière suivante (dans le cas ici d’une liste d’entiers).
351 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
352 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
1. void aff_lst(Liste lst, void (*aff_elt)(void *));
352 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
1. void aff_lst(Liste lst, void (*aff_elt)(void *));
352 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
1. void aff_lst(Liste lst, void (*aff_elt)(void *));
352 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
1. void aff_lst(Liste lst, void (*aff_elt)(void *));
352 / 399
Listes génériques
Beaucoup de fonctions sur les listes peuvent ainsi être rendues génériques. Entre autres :
1. void aff_lst(Liste lst, void (*aff_elt)(void *));
Les cas 3 et 4 supposent que les éléments représentés par les listes sont comparables au moyen
d’une fonction est_inf à fournir.
352 / 399
Plan
Pointeurs de fonction
Principe
En paramètre et en retour
Généricité
Implantation de monoïdes
353 / 399
Monoïdes
354 / 399
Monoïdes
354 / 399
Monoïdes
354 / 399
Monoïdes
354 / 399
Monoïdes
Exemples :
I (N, +, 0) est le monoïde additif des entiers naturels ;
354 / 399
Monoïdes
Exemples :
I (N, +, 0) est le monoïde additif des entiers naturels ;
I (N, ×, 1) est le monoïde multiplicatif des entiers naturels ;
354 / 399
Monoïdes
Exemples :
I (N, +, 0) est le monoïde additif des entiers naturels ;
I (N, ×, 1) est le monoïde multiplicatif des entiers naturels ;
I ({a, b}∗ , ·, ) est le monoïde libre sur les lettres a et b. Ses éléments sont les chaînes de
caractères composées de a et de b. Son produit · est la concaténation et son unité est la
chaîne vide.
354 / 399
Implantation de monoïdes
355 / 399
Implantation de monoïdes
1. implanter une structure de donnée générique pour représenter des monoïdes dont la
nature des éléments n’est pas connue à l’avance ;
355 / 399
Implantation de monoïdes
1. implanter une structure de donnée générique pour représenter des monoïdes dont la
nature des éléments n’est pas connue à l’avance ;
2. implanter une fonction générique qui élève à la puissance n > 0 un élément x d’un
monoïde.
355 / 399
Structure de donnée générique de monoïde
356 / 399
Structure de donnée générique de monoïde
Le champ produit est un pointeur de fonction vers une fonction qui réalise le produit du
monoïde.
356 / 399
Définition du monoïde additif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, +, 0) et dont le type de
retour et la signature respectent ceux du champ produit :
357 / 399
Définition du monoïde additif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, +, 0) et dont le type de
retour et la signature respectent ceux du champ produit :
357 / 399
Définition du monoïde additif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, +, 0) et dont le type de
retour et la signature respectent ceux du champ produit :
357 / 399
Définition du monoïde additif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, +, 0) et dont le type de
retour et la signature respectent ceux du champ produit :
357 / 399
Définition du monoïde additif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, +, 0) et dont le type de
retour et la signature respectent ceux du champ produit :
357 / 399
Définition du monoïde multiplicatif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, ×, 1) et dont le type de
retour et la signature respectent ceux du champ produit :
358 / 399
Définition du monoïde multiplicatif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, ×, 1) et dont le type de
retour et la signature respectent ceux du champ produit :
358 / 399
Définition du monoïde multiplicatif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, ×, 1) et dont le type de
retour et la signature respectent ceux du champ produit :
358 / 399
Définition du monoïde multiplicatif des entiers naturels
On commence par définir une fonction qui réalise le produit de (N, ×, 1) et dont le type de
retour et la signature respectent ceux du champ produit :
358 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Définition du monoïde libre sur a et b
On commence par définir une fonction qui réalise le produit de ({a, b}∗ , ·, ) et dont le type de
retour et la signature respectent ceux du champ produit :
359 / 399
Fonction générique d’exponentiation
360 / 399
Fonction générique d’exponentiation
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
tmp = puissance(m, x, n / 2);
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
tmp = puissance(m, x, n / 2);
if (n % 2 == 0)
return m.produit(tmp, tmp);
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
tmp = puissance(m, x, n / 2);
if (n % 2 == 0)
return m.produit(tmp, tmp);
return m.produit(x, m.produit(tmp, tmp));
}
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
tmp = puissance(m, x, n / 2);
if (n % 2 == 0)
return m.produit(tmp, tmp);
return m.produit(x, m.produit(tmp, tmp));
}
Cette fonction renvoie un pointeur générique sur une variable contenant le résultat de la valeur à
l’adresse x élevée à la puissance n , pour le produit spécifié par le monoïde m .
360 / 399
Fonction générique d’exponentiation
if (n == 0)
return m.unite;
tmp = puissance(m, x, n / 2);
if (n % 2 == 0)
return m.produit(tmp, tmp);
return m.produit(x, m.produit(tmp, tmp));
}
Cette fonction renvoie un pointeur générique sur une variable contenant le résultat de la valeur à
l’adresse x élevée à la puissance n , pour le produit spécifié par le monoïde m .
Celle fonction utilise l’algorithme dit d’exponentiation rapide.
360 / 399
Application de la fonction d’exponentiation
int res, val = 5;
res = *((int *) puissance(m_add, &val, 3));
printf("%d\n", res);
361 / 399
Application de la fonction d’exponentiation
int res, val = 5;
res = *((int *) puissance(m_add, &val, 3));
printf("%d\n", res);
361 / 399
Application de la fonction d’exponentiation
int res, val = 5;
res = *((int *) puissance(m_add, &val, 3));
printf("%d\n", res);
Génération aléatoire
Générateurs aléatoires
Nombres pseudo-aléatoires
Utilisation et implantation
362 / 399
Plan
Génération aléatoire
Générateurs aléatoires
Nombres pseudo-aléatoires
Utilisation et implantation
363 / 399
Générateurs aléatoires
Un générateur aléatoire sur un ensemble E d’objets est une fonction g à valeur dans E non
déterministe. La valeur renvoyée par g n’est pas nécessairement la même d’un appel à un autre.
On cherche dans la mesure du possible à faire en sorte que, étant donné un ensemble E d’objets,
tout objet de E ait la même probabilité d’être renvoyé par le générateur aléatoire g. On parle
alors de générateur aléatoire uniforme.
Par exemple, si E est l’ensemble des faces d’un dé à six faces, le procédé g qui consiste à lancer le
dé et renvoyer la face visible constitue un générateur aléatoire uniforme (et non uniforme si le dé
est pipé).
364 / 399
Intérêt des générateurs aléatoires
Les générateurs aléatoires offrent de nombreuses applications. Ils sont utilisés pour :
1. les méthodes de Monte-Carlo pour obtenir des solutions approchées à des problèmes ;
2. tester des algorithmes en générant des entrées de manière aléatoire (permutations, listes,
arbres, graphes, etc.) ;
3. rompre le caractère déterministe d’un programme en y incluant des éléments
imprévisibles (dans les jeux par exemple) ;
4. générer des mots de passe ou des clés de chiffrement.
365 / 399
Universalité des générateurs aléatoires d’entiers
Pour construire un générateur aléatoire sur un ensemble E d’objets, il suffit en pratique d’avoir
un générateur aléatoire d’entiers d’ordre #E.
En effet, il suffit de placer les objets (ou mieux : les adresses des objets) de E dans un tableau
e0 e1 ... en−1 ,
d’appeler g et de renvoyer l’objet du tableau figurant à l’indice spécifié par l’entier généré par g.
Ainsi, pour faire de la génération aléatoire d’objets, il est suffisant (en 1re approximation) de
savoir construire des générateurs aléatoires d’entiers d’un ordre suffisamment grand.
366 / 399
Réduire l’ordre d’un générateur aléatoire d’entiers
L’opérateur « modulo » offre un moyen très simple pour réduire l’ordre d’un générateur
aléatoire d’entiers.
En effet, supposons que l’on dispose d’un générateur aléatoire d’entiers g d’ordre n. On souhaite
obtenir un générateur aléatoire d’entiers h d’ordre k avec 1 6 k 6 n. On pose pour cela
h := g mod k.
Il est clair que les entiers générés par h sont dans l’ensemble {0, . . . , k − 1}. On a ainsi réduit
l’ordre du générateur g.
367 / 399
Réduction de l’ordre et uniformité
h: 0 1 2
Il y a ainsi deux manières de générer 0 par h (probabilité de 12 ) alors qu’il n’y en a qu’une seule
pour 1 et 2 (probabilités resp. de 14 ).
368 / 399
Mesure de la non uniformité de la réduction
Soient g un générateur aléatoire uniforme d’ordre n et h la réduction de g à l’ordre k, où k 6 n.
Notons genh (i) le nombre de manières de générer l’entier i 6 k − 1 par h. Alors, on voit
facilement que (
b nk c + 1 si i ∈ {0, . . . , (n mod k) − 1}
genh (i) =
b nk c sinon.
La probabilité Ph (i) de générer l’entier i 6 k − 1 par h vérifie
genh (i)
Ph (i) = .
n
Les entiers i de l’ensemble {0, . . . , k − 1} ont ainsi des probabilités différentes d’être générés. Ils
se divisent en deux classes :
1. les petits, de 0 à (n mod k) − 1, avec une probabilité plus grande ;
2. les grands, de (n mod k) à k − 1, avec une probabilité plus petite.
369 / 399
Mesure de la non uniformité de la réduction
Comparons numériquement les probabilités de génération des petits et des grands nombres pour
différentes valeurs de n et de k :
n k Prob. des petits Prob. des grands Rapport
4 3 0.5 0.25 2
400 300 0.005 0.0025 2
400 200 0.005 0.005 1
400 101 0.01 0.0075 ' 1.3333
400 99 0.0125 0.01 1.25
500 99 0.012 0.01 1.2
5000 99 0.0102 0.01 1.02
50000 99 0.01012 0.0101 ' 1.00198
On observe que plus k est petit par rapport à n, plus h se rapproche d’un générateur uniforme.
Lorsque k est un diviseur de n, l’uniformité est immédiate.
En pratique, la réduction est considérée comme préservant l’uniformité, à condition de partir
d’un générateur aléatoire d’entiers uniforme g d’ordre le plus grand possible.
370 / 399
Plan
Génération aléatoire
Générateurs aléatoires
Nombres pseudo-aléatoires
Utilisation et implantation
371 / 399
Machines et aléatoire
Un ordinateur est par essence une machine déterministe : le résultat d’un programme est
toujours le même s’il est exécuté deux fois dans les mêmes conditions.
Il est ainsi impossible d’écrire un programme qui implante un générateur aléatoire d’entiers.
La seule chose qu’il est possible de faire est de programmer une fonction qui semble le plus
possible se comporter comme un générateur aléatoire. On parle alors de générateur
pseudo-aléatoire.
Les entiers qu’un générateur pseudo-aléatoire génère lorsqu’on l’appelle plusieurs fois de suite
forme une suite. On appelle cette suite une suite d’entiers pseudo-aléatoires.
372 / 399
Principe de fonctionnement
à partir de la graine dans laquelle figurent les données initiales et de la règle qui explique
comment générer le prochain entier de la suite en se basant sur les entiers précédemment
générés.
373 / 399
Qualités d’un générateur pseudo-aléatoire
Un bon générateur pseudo-aléatoire produit une suite d’entiers qui semble aléatoire.
Il est possible de mesurer la qualité d’un générateur aléatoire conformément à plusieurs critères :
1. uniformité de la génération ;
2. ses périodes (longueurs des cycles de génération) ;
3. sensibilité à la graine (existence de mauvaises graines) ;
4. corrélation entre un entier engendré et les précédents ;
5. analyse de la distribution en plusieurs dimensions.
374 / 399
Méthode du carré médian
Le générateur pseudo-aléatoire g utilisant la méthode du carré médian fonctionne de la manière
suivante.
La graine de ce générateur est un entier compris entre 0 et 9999. Si gi−1 est l’entier
précédemment généré (ou bien la graine), le prochain entier généré est
j g k 2
i−1
gi := mod 100 .
10
On obtient p.ex. les suites
Graine Suite
1234 529, 2704, 4900, 8100, 100, 100, ...
3733 5329, 1024, 4, 0, 0, ...
9999 9801, 6400, 1600, 3600, 3600, ...
C’est un mauvais générateur pseudo-aléatoire. Les périodes sont bien trop courtes et il y a
des mauvaises graines (comme 0).
375 / 399
Méthode additive
Ce générateur pseudo-aléatoire est bien meilleur que le précédent mais échoue à de nombreux
tests statistiques.
376 / 399
Générateurs congruentiels linéaires
Sous réserve de choisir des bons paramètres n, a et b, ce générateur pseudo-aléatoire est plutôt
bon. Il est très utilisé en pratique.
377 / 399
Plan
Génération aléatoire
Générateurs aléatoires
Nombres pseudo-aléatoires
Utilisation et implantation
378 / 399
Les fonctions rand et srand
La fonction
int rand();
379 / 399
Modèle d’utilisation de rand et de srand
La fonction time_t time (time_t* timer); du module time , lorsque appelée avec l’argument NULL
renvoie le temps écoulé en secondes depuis le 1er janvier 1970 à minuit, UTC.
Considérer cette valeur est donc un bon moyen d’initialiser la graine du générateur.
Attention (point très important) : il est totalement erroné d’initialiser dans un même projet deux
fois la graine. Le seul appel à srand doit figurer dans main et nulle part ailleurs.
380 / 399
Implantation avec variable globale
Une implantation possible des fonctions rand et srand s’appuie sur une variable globale pour
mémoriser la dernière valeur générée.
/∗ RandPerso . c ∗/
#include "RandPerso.h"
/∗ RandPerso . h ∗/
#ifndef __RAND_PERSO__ unsigned int valeur_rand =
#define __RAND_PERSO__ RAND_PERSO_GRAINE;
381 / 399
Implantation avec variable statique
Dans cette implantation, on utilise une variable statique pour mémoriser la dernière valeur
générée.
/∗ RandPerso . c ∗/
#include "RandPerso.h"
L’initialisation ne se fait qu’au 1er appel et l’utilisateur n’a pas à la faire explicitement. Avec cette méthode,
il n’est pas possible de fixer la graine.
382 / 399
Retrouver le déterminisme
Pour pouvoir reproduire les résultats fournis par un programme utilisant la fonction rand , il est
nécessaire de proposer un mode de fonctionnement déterministe dans lequel la graine a été fixée.
Pour cela, on met en place dans le module principal d’un projet et dans sa fonction main le
mécanisme suivant :
Meilleure solution : utiliser l’option -D de gcc . Il suffit de supprimer la ligne #define DETERMINISTE et de compiler
avec -DDETERMINISTE pour définir la macro en question.
383 / 399
Plan
Mémoïsation
Variables statiques
Mémoïsation
384 / 399
Plan
Mémoïsation
Variables statiques
Mémoïsation
385 / 399
Notion de variable statique
Une variable statique est une variable dont la durée de vie est égale à celle de l’exécution du
programme. Une telle variable est créée et initialisée à la compilation.
L’instruction
static T ID;
Elles sont les contreparties des variables automatiques dont la durée de vie s’étend à
l’exécution du bloc dans lesquels elles se trouvent et dont la création se fait à l’exécution le
moment voulu.
Les variables automatiques sont les variables habituelles (se déclarent sans le mot clé static ).
386 / 399
Persistance des variables statiques
Une variable statique est rémanente : elle conserve sa dernière valeur jusqu’à une éventuelle
modification.
Ici, s est une variable statique. La valeur 3 (initialisation) ne lui est attribuée qu’une fois, lors
de la compilation.
De plus, s conserve sa valeur (qui peut être modifiée) au fil de l’exécution, d’appel en appel à
fct .
387 / 399
Géographie de la mémoire lors d’une exécution
Lors de l’exécution d’un programme, la mémoire qui lui est consacrée est divisée de la manière
suivante.
388 / 399
Compter le nombre d’appels à une fonction
Étant donnée une fonction, on souhaite compter le nombre de fois qu’elle a été appelée depuis le
lancement du programme.
À chaque instant, la variable statique nb_app a pour valeur le nombre de fois que fact a été
appelée.
Problème : cette variable a pour portée lexicale le corps de la fonction fact et est invisible
ailleurs. On ne peut donc pas la lire depuis l’extérieur.
389 / 399
Compter le nombre d’appels à une fonction
Pour avoir accès à la valeur de nb_app hors du corps de fact , on la transmet à l’aide d’un
passage par adresse.
1 int fact(int n, int *nb) {
2
3 /∗ Mecanisme de comptage ∗/
4 static int nb_app = 0;
5 nb_app += 1;
6 *nb = nb_app;
7
8 if (n <= 1) return 1;
9 return n * fact(n - 1, nb);
10 }
390 / 399
Plan
Mémoïsation
Variables statiques
Mémoïsation
391 / 399
Principe de la mémoïsation
Soit f une fonction sans effet de bord et déterministe (sa valeur de retour ne dépend que de
ses arguments).
Observation : si l’on appelle f deux fois avec des mêmes arguments, elle renverra deux fois la
même valeur.
Conséquence : comme il est inutile (et inefficace) de refaire deux fois un même calcul, il suffit de
mémoriser le résultat renvoyé par f lors du 1er appel et de le renvoyer sans calcul lors du 2e.
Marche à suivre : on enregistre toutes les valeurs de retour de f au fur et à mesure de ses
appels.
392 / 399
Détails sur la mémoïsation
Les résultats renvoyés par f sont mémorisés dans une table de hachage mem associant à
chaque jeu d’arguments avec lequel elle est appelée la valeur de retour de f .
Il est nécessaire de disposer d’une fonction de hachage h associant à chaque jeu d’arguments
un entier.
Il ne doit y avoir qu’une seule table mem pour f , dont la durée de vie est égale à l’exécution du
programme et rémanente : mem est ainsi une variable statique, locale à f .
393 / 399
Procédé général de mémoïsation d’une fonction
Soit f une fonction à n paramètres.
(2) sinon,
(a) calculer la valeur de retour de f(a_1, ..., a_n) ;
(b) mémoriser cette valeur dans mem ;
(c) renvoyer cette valeur.
394 / 399
Exemple de mémoïsation d’une fonction à un paramètre
On souhaite mémoïser la fonction
1 int fibo(int n) {
2 if (n <= 1) return n;
3 return fibo(n - 1) + fibo(n - 2);
4 }
Cette fonction est paramétrée par un entier et renvoie un entier. La table mem est ainsi un tableau
d’entiers. La valeur de hachage d’un argument n est n .
On l’utilise de la manière suivante :
1. elle est de taille T_MEM où T_MEM est une constante suffisamment grande ;
395 / 399
Exemple de mémoïsation d’une fonction à un paramètre
396 / 399
Mémoïsation de fonctions à plusieurs paramètres
La table mem devient un tableau de variables d’un type structuré E_f composé
1. d’un champ pour chaque paramètre de f ;
2. d’un champ pour le résultat renvoyé par f appelé avec les arguments spécifiés par les
précédents champs ;
3. d’un champ booléen indiquant si le résultat de f appelée avec les arguments spécifiés par
les précédents champs a bien été calculé.
Il faut de plus disposer d’une fonction de hachage h_f de même signature que f et qui renvoie
un entier positif.
Elle permet d’attribuer à chaque jeu d’arguments une position dans la table mem .
397 / 399
Exemple de mémoïsation d’une fonction à deux paramètres
On souhaite mémoïser la fonction
1 int puiss(int n, int p) {
2 if (p == 0) return 1;
3 return n * puiss(n, p - 1);
4 }
Celle-ci est paramétrée par deux entiers. Le type structuré E_puiss est donc défini par
1 typedef struct {
2 int n; /∗ 1 er parametre ∗/
3 int p; /∗ 2e parametre ∗/
4 int res; /∗ Resultat ∗/
5 int ok; /∗ Indique si le calcul a deja ete fait ∗/
6 } E_puiss;
398 / 399
Exemple de mémoïsation d’une fonction à deux paramètres
399 / 399