ch3 Pointeurs (Mode de Compatibilité)
ch3 Pointeurs (Mode de Compatibilité)
ch3 Pointeurs (Mode de Compatibilité)
Département d’Informatique
La notion de
Type Abstrait de Données
(TDA)
Said EL GAROUANI
1
Les mémoires...
2
Mémoire et exécution
Pile
Piles d’appels de fonctions
Repré
ésentation de tableaux: accè
ès direct et indexé
é
Ré
éfé
érences croisé
ées
Int *a;
a
5
Rappels sur les pointeurs
malloc(3*sizeof(int));
7
Rappels sur les pointeurs
Int *a = malloc
malloc(3*
(3*sizeof
sizeof((int
int));
));
Int *a = (int
(int*)
*)malloc
malloc(3*
(3*sizeof
sizeof(
(int
int));
));
a *a
8
Rappels sur les pointeurs
free(a); a = NULL;
a *a
Désallocation dynamique
9
Rappels sur les pointeurs
Int *a = (int
(int*)
*)malloc
malloc(3*
(3*sizeof
sizeof((int
int));
));
Int *a = (int
(int*)
*)calloc
calloc(3,
(3, sizeof
sizeof((int
int));
));
a = (int
(int*)
*)realloc
realloc(4*
(4*sizeof
sizeof((int
int));
));
10
Rappel sur Pointeurs de fonction
Déclaration :
type (* nom_de_fonction) ([arguments]);
Utilisation :
(* nom_de_fonction) (arg1, arg2, arg3,...);
11
Expemple de Pointeurs de fonction
short tab[10]
int main() {
for( i = 0; i < 10; i++ ) {
tab[i] = n;
}
imprimer( 10, carre );
}
12
TDA
• Description d’un type de données grâce à
– La liste des opérations liées à ce type
– Pour chaque opération :
• Données d’entrée/sortie
• Préconditions, etc.
• Algorithme
tb1[0] tb1[1] …
• Exemple 1:
tb1 + 4 correspond à l’adresse de la 1ère case à laquelle on
ajoute 4 adresses, c’est-à-dire à &(tb1[4])
⇒ tb1 ou tb1 + 0 ⇔ &(tb1[0])
tb1 + 1 ⇔ &(tb1[1])
tb1 + 2 ⇔ &(tb1[2])
…
18
Arithmétique d’adresses dans les
tableaux (suite)
• Exemple 2 :
&(tb1[5]) – 2 correspond à l’adresse de tb1[5] à
laquelle on soustrait 2 adresses ⇒ &(tb1[3])
• Exemple 3 :
&(tb1[7]) – &(tb1[3]) correspond au nombre
d’adresses entre tb1[7]) et tb1[3]) ⇒ 4
19
Notion de pointeur
• Un pointeur est une variable permettant de stocker une
adresse-mémoire
• Déclaration :
type *pointeur ;
⇒ le type est celui des variables auxquelles permet
d’accéder le pointeur
• Exemples :
int *pt_int ; /* pointeur sur un entier */
char *pt_char
*pt_char;; /* pointeur sur un caractère */
20
Affectation d’un pointeur
• Affectation de l’adresse d’une variable :
int x, *px ;
px = &x ;
21
Arithmétique de pointeurs
• L’addition et la soustraction fonctionnent
comme avec les adresses…
int tb1[10], *p1, *p2 ;
p1 = tb1 + 3 ; /* p1 pointe sur tb1[3] */
p2 = p1 – 1 ; /* p2 pointe sur tb1[2] */
22
Opérateur *
• Permet d’accéder au contenu d’une variable
par un pointeur :
int x, y, *p ;
x = 10 ;
p = &x ;
y = *p ; /* y doit contenir 10 */
23
Notion de pointeur
int i = 3
int *p;
p = &i;
objet adresse valeur
i 431836000 3
p 431836004 431836000
24
Notion de pointeur
main() main()
{ {
avant la dernière ligne des deux programmes
int i = 3, j = 6; int i = 3, j = 6;
int *p1, *p2; objet adresse valeur int *p1, *p2;
i 431836000 3 p1 = &i;
p1 = &i;
j 431836004 6
p2 = &j; p2 = &j;
p1 431835984 431836000
*p1 = *p2; p2 4831835992 431836004 p1 = p2;
} }
25
Types évolués
• Types construits par l’utilisateur en
assemblant divers types de valeurs :
– Énumérations
– Unions
– Structures
– Fonctions
26
Énumérations
• Définition d’un type par spécification d’un ensemble de valeurs
possibles
• Une énumération est un type de données dont les valeurs sont
des constantes nommées.
• Syntaxe :
enum nom_type {val1, val2, val3, …} ;
Ou
enum nom_type {val1=entier1, val2=entier2, …} ;
⇒ Définition en dehors de toute fonction
• Ex. :
enum couleur {ROUGE, VERT, BLEU} ;
⇒ Par défaut, ROUGE est associé à 0, VERT à 1, …
enum booleen {faux = 12, vrai = 23};
27
Énumérations
• Exemple de variable de type énuméré :
enum couleur c ;
…
c = BLEU ;
enum JourDeSemaine
{ LUNDI, MARDI, MERCREDI, JEUDI, VENDREDI, SAMEDI, DIMANCHE };
• Combinaison avec typedef pour éviter d’avoir à écrire
tout le temps enum (pour alléger l'écriture des programmes on
affecte un nouvel identificateur à un type composé):
typedef type synonyme;
typedef enum couleur Couleur ;
…
Couleur c ;
c = BLEU ;
28
Allocation dynamique
• Fonction malloc :
malloc (nombre_octets
nombre_octets))
⇒ Alloue une zone de taille nombre_octets octets en mémoire et
retourne un pointeur de type char* qu'il faut convertir à l'aide
d'un cast.
cette fonction se trouve dans la librairie standard stdlib.h
• L'argument nombre_octets est souvent donné à l'aide de la
fonction sizeof() qui renvoie le nombre d'octets utilisés pour stocker
un objet.
• Ex : int * p;
p = (int*)malloc(sizeof(int)); en principe sizeof(int)=4
29
Allocation dynamique
32
Allocation dynamique
• S’il n’y a plus de place en mémoire, malloc
retourne l’adresse NULL (constante définie
dans la bibliothèque <malloc.h
malloc.h>>)
⇒ À tester systématiquement…
33
Pointeurs et tableaux à plusieurs dimensions
• Il s'agit de définir un pointeur de pointeur
• int tab[M][N];
– tab pointe vers le premier élément du tableau sa
valeur est &tab[0][0]
– tab[i], pour i entre 0 et M-1, pointe vers le premier
élément de la ligne d'indice i; tab[i] a la valeur
&tab[i][0]
• type **nom-du-pointeur;
• type ***nom-du-pointeur; équivaut à un tableau à 3
dimensions
34
Pointeurs et tableaux à plusieurs dimensions
main()
{
int k, n;
int **tab;
tab = (int**)malloc(k * sizeof(int*));
for (i = 0; i < k; i++)
tab[i] = (int*)malloc(n * sizeof(in));
…
for ( i = 0; i < k;i++)
free(tab[i]);
free(tab);
} 35
Pointeurs et chaînes de caractères
36
Pointeurs et chaînes de caractères
#include<stdio.h>
main()
{ int i;
char *chaine;
chaine = "chaine de caracteres";
for (i = 0; i < *chaine !='\0'; i++)
chaine++;
printf("nombre de caracteres = %d\n",i);
}
équivaut à strlen(chaine) de la librairie standard string.h.
37
Pointeurs et chaînes de caractères
• Avec les pointeurs de caractères on peut concaténer deux chaînes.
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
main()
{ int i;
char *chaine1, *chaine2, *res, *p;
chaine1 = "chaine ";
chaine2 = "de caracteres";
res = (char*)malloc((strlen(chaine1) + strlen(chaine2)) * sizeof(char));
p =res;
for (i = 0; i < strlen(chaine1); i++)
*p++ = chaine1[i];
for (i = 0; i < strlen(chaine2); i++)
*p++ = chaine2[i];
printf("%s\n",res);
}
38
Pointeurs et chaînes de caractères
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
main()
{ int i;
char *chaine1, chaine2, *res, *p;
chaine1 = "chaine ";
chaine2 = "de caracteres";
res = (char*)malloc((strlen(chaine1) + strlen(chaine2)) * sizeof(char));
p =res;
for (i = 0; i < strlen(chaine1); i++)
*res++ = chaine1[i];
for (i = 0; i < strlen(chaine2); i++)
*res++ = chaine2[i];
printf("%s\n",res);
}
39
Unions
• Même fonctionnement que la structure, mais un seul
emplacement mémoire est alloué pour l’ensemble des
champs
⇒ Utilisée pour une donnée pouvant être de différents types (à des
moments différents)
union nom_type {
type1 champ1 ;
type2 champ2 ;
…
};
⇒ La taille mémoire est celle du type le plus grand parmi les champs
40
Unions
• Exemple :
union jour {
char lettre;
int numero;
};
main()
{
union jour hier, demain;
hier.lettre = 'J';
printf("hier = %c\n",hier.lettre);
hier.numero = 4;
demain.numero = (hier.numero + 2) % 7;
printf("demain = %d\n",demain.numero);
}
41
Unions
• Exemple :
union contenu {
int val_entier ;
float val_reel ;
char val_car ;
};
typedef union contenu Contenu ;
42
Unions - Exemple
enum typeDrapeau {ENTIER, REEL, CAR} ;
typedef enum typeDrapeau TypeDrapeau ;
union contenu {
int val_entier ;
float val_reel ;
char val_car ;
};
typedef union contenu Contenu ;
struct element {
TypeDrapeau drapeau ;
Contenu val ;
};
typedef struct element Element ;
43
Unions - Exemple
Ou :
void afficher_element (Element x)
{ void afficher_element (Element x)
switch (x.drapeau
x.drapeau)) {
{
case ENTIER:
if (x.drapeau
(x.drapeau == ENTIER)
printf("Valeur
printf ("Valeur = %
%dd\n",
x.val.val_entier
x.val.val_entier)) ; printf("Valeur
printf ("Valeur = %
%d
d\n",
break; x.val.val_entier
x.val.val_entier)) ;
case REEL: else if (x.drapeau
(x.drapeau == REEL)
printf("Valeur
printf ("Valeur = %g
%g\n", printf("Valeur
printf ("Valeur = %g
%g\n",
x.val.val_reel
x.val.val_reel)) ; x.val.val_reel
x.val.val_reel)) ;
break; else if (x.drapeau
(x.drapeau == CAR)
case CAR:
printf("Valeur
printf ("Valeur = %
%cc\n",
printf("Valeur
printf ("Valeur = %
%cc\n",
x.val.val_car
x.val.val_car)) ;
x.val.val_car
x.val.val_car)) ;
break;
else
default: printf("Donnée
printf ("Donnée inconnue
printf("Donnée
printf ("Donnée inconnue !!\\n"); !\n");
} }
}
44
Structures
• Équivalent de l’enregistrement en algorithmique
• Syntaxe :
struct nom_type {
type1 champ1 ;
type2 champ2 ;
type3 champ3 ;
…
};
• déclaration : struct nom_type objet;
45
Structures
• Ou :
struct nom_type {
type1 champ1 ;
type2 champ2 ;
type3 champ3 ;
…
} objet ;
46
Structures
• Exemple de structure :
struct etudiant {
int INE ;
char nom [80], prenom [80] ;
};
• Exemple de déclarations de variables :
struct etudiant e ;
struct etudiant tabEtudiants [100] ; /* tableau de structures */
• Initialisation à la déclaration :
struct etudiant e = {70081, ""Baha
Baha",
", "Med"};
47
Structures
• Utilisation de typedef :
typedef struct etudiant Etudiant ;
…
Etudiant e ;
49
Pointeurs sur des structures
50
#include<stdlib.h>
Pointeurs sur des structures
#include<stdlib.h>
#include<stdio.h>
#include<stdio.h>
struct eleve{
struct eleve{
char nom[20];
char nom[20];
int date; };
int date; };
typedef struct eleve *classe;
typedef struct eleve *classe;
main()
main()
{ int n, i;
{ int n, i;
classe tab;
classe tab;
printf("nombre d'eleves de la classe = ");
printf("nombre d'eleves de la classe = ");
scanf("%d",&n);
scanf("%d",&n);
tab = (classe)malloc(n * sizeof(struct eleve));
tab = (classe)malloc(n * sizeof(struct eleve));
for (i = 0 ; i < n; i++)
for (i = 0 ; i < n; i++)
{ printf("\n saisie de l'eleve
numero %d\n",i); { printf("\n saisie de l'eleve
numero %d\n",i);
printf("nom de l'eleve = ");
printf("nom de l'eleve = ");
scanf("%s",&tab[i].nom);
scanf("%s",&tab[i].nom);
printf("\n date de naissance
JJMMAA = "); printf("\n date de naissance
JJMMAA = ");
scanf("%d",&tab[i].date);
scanf("%d",&tab[i].date);
}
}
printf("\n Entrez un numero ");
printf("\n Entrez un numero ");
scanf("%d",&i);
scanf("%d",&i);
printf("\n Eleve numero %d : ",i);
printf("\n Eleve numero %d : ",i);
printf("\n nom =%s",tab[i].nom);
printf("\n nom =%s",(tab +i)->nom);
printf("\n date de naissance = %d\n",tab[i].date);
printf("\n date de naissance = %d\n",(tab + i)->date);
free(tab);
free(tab);
}
} 51
Structures et fonctions
52
Structures imbriquées
Imbrication d’une structure dans une autre structure :
struct date {
int jour, mois, annee
annee;;
};
typedef struct date Date ;
struct etudiant {
int INE ;
char nom[80], prenom[80];
prenom[80];
Date date_naissance ;
};
typedef struct etudiant Etudiant ;
53
Structures imbriquées
• On peut toujours initialiser à la déclaration :
Etudiant e = {70081, "Baha
"Baha",", « Med", {12, 06, 1978} } ;
54
Structures auto-référentes
• Utilisation d’un champ qui soit du même type que la structure :
struct etudiant {
int INE ;
char nom [80], prenom [80] ;
struct etudiant binome ;
};
⇒ INCORRECT (le compilateur ne connaît pas la taille mémoire à
réserver pour le champ binome)
binome
• Utilisation de l’adresse :
struct etudiant {
int INE ;
char nom [80], prenom [80] ;
struct etudiant *binome ;
};
55
Chaînage par structures auto-
référentes
• Principe utilisé pour les listes chaînées :
struct cellule {
… /* champs correspondants au contenu */
struct cellule *suiv
*suiv ;
};
56
Schéma du chaînage obtenu
57
Chaînage supplémentaire
• Sur le même principe…
struct cellule {
… /* champs correspondants au contenu */
struct cellule *suiv ;
struct cellule *prec ;
};
Ou
struct cellule {
… /* champs correspondants au contenu */
struct cellule *filsgauche ;
struct cellule *filsdroit ;
};
58
Chaînage et allocation dynamique
59
Directives
60
Directive #include
• Permet d'incorporer dans le fichier source le texte figurant
dans un autre fichier :
– fichier en tête de la librairie standard (stdio.h, math.h, …)
– n'importe quel autre fichier
• Syntaxe ,:
– #include<nom-de-fichier> : fichier se trouvant dans les répertoires
systèmes (ex : /usr/include/)
– #include "nom-de-fichier" : fichier dans le répertoire courant
• Possibilité de spécifier d'autres répertoires à l'aide de
l'option -I du compilateur.
61
Directive #define
• Permet de définir :
– des constantes symboliques
• #define nom reste-de-la-ligne : demande au préprocesseur de
substituer toute occurrence de nom par la chaîne reste-de-la-ligne
• ex : #define NB_LIGNES 10
#define NB_COLONNES 33
#define TAILLE_MATRICE NB_LIGNES * NB_COLONNES
– des macros avec paramètres
• #define nom(liste-de-paramètres) corps-de-la-macro : où liste-de-
paramètres est une liste d'identificateurs séparés par des virgules
• ex : #define MAX(a,b) (a>b ? a : b)
le préprocesseur remplacera toutes les occurrences du
type MAX(x,y) par (x > y ? x : y)
62
Directive #define
• Exemple :
#define IF if( #define FOR for(
#define THEN ){ #define WHILE while(
#define ELSE } else { #define DO ){
#define ELIF } else if ( #define OD ;}
#define FI ;} #define REP do{
#define BEGIN { #define PER }while(
#define END } #undef DONE
#define SWITCH switch( #define DONE );
#define IN ){ #define LOOP for(;;){
#define ENDSW } #define POOL }
• Et voici un exemple de code :
assign(n,v)
NAMPTR n;
STRING v;
{
IF n->namflg&N_RDONLY
THEN failed(n->namid,wtfailed);
ELSE replace(&n->namval,v);
FI
}
63
La compilation conditionnelle
• A pour but d'incorporer ou d'exclure des parties du
code source dans le texte qui sera généré par le
préprocesseur
• Permet d'adapter le programme au matériel ou à
l'environnement sur lequel il s'exécute, ou
d'introduire dans le programme des instructions de
débogage
• directive en 2 catégories liée :
– à la valeur d'une expression
– à l'existence ou l'inexistence de symboles
64
La compilation conditionnelle
• Condition liée à la valeur d'une expression
#if codition-1
partie-du-programme-1
#elif condition-2
partie-du-programme-2
#elif condition-n
partie-du-programme-n
#else partie-du-programme-∞
– le nombre de #elif est quelconque et le #else est facultatif
– chaque condition-i doit être une expression constante
– une seule partie-du-programme sera compilée : celle qui correspond
à la première condition-i non nulle
65
La compilation conditionnelle
– Exemple :
#define PROCESSEUR ALPHA
#if PROCESSEUR == ALPHA
taille_long = 64;
#elif PROCESSEUR == PC
taille_long = 32;
#endif
• Condition liée à l'existence d'un symbole
#ifdef symbole
partie-du-programme-1
#else
partie-du-programme-2
#endif
– si symbole est défini au moment où l'on rencontre la directive
#ifdef, alors partie-du-programme-1 sera compilée dans le cas
contraire
66
La compilation conditionnelle
• Exemple :
#define DEBUG
...
#ifdef DEBUG
for (i = 0; i < N; i++)
printf("%d\n",i);
#endif
– si la ligne #define DEBUG existe, l'instruction for sera compilée
67
Définition d'une fonction
• C'est la donnée du texte de son algorithme qu'on
appelle corps de la fonction.
type nom-fonction ( type-1 arg-1, …, type-n arg-n)
{
[déclarations de variables locales]
liste d'instructions
}
• type désigne le type de la fonction i.e. le type de la
valeur qu'elle retourne
• si la fonction ne renvoie pas de valeur elle est de type
void.
68
Définition d'une fonction
• La fonction se termine par l'instruction return :
– return(expression); expression du type de la fonction
– return; fonction sans type
• Ex :
int produit (int a, int b) int puissance (int a, int n)
{ {
return(a * b); if ( n == 0)
return(1);
}
return(a * puissance(a, n-1));
void imprime_tab (int *tab, int nb_elements) }
{
int i;
for ( i = 0; i < nb_elements, i++)
printf("%d \t",tab[i]);
printf("\n");
return;
}
69
Appel d'une fonction
• L'appel de fait par:
– nom-fonction(para-1,para-2, …, para-n);
70
Déclaration d'une fonction
int puissance (int , int)p;
main()
{
int a = 2, b = 5;
printf("%d\n",puissance(a,b));
}
71
Durée de vie des variables
• Les variables manipulées dans un programme C n'ont pas
la même durée de vie. 2 catégories de variables :
– variables permanentes (ou statiques) : occupent une place
mémoire durant toute l'exécution du programme (segment de
données). Elles sont initialisées à zéro par le compilateur et elles
sont caractérisées par le mot-clef static.
– variables temporaires : se voient allouer une place mémoire de
façon dynamique (segment de pile). Elles ne sont pas initialisées.
Leur place mémoire est libérée à la fin d'exécution de la fonction
secondaire. Variables dites automatique. Elles sont spécifiées par
le mot-clef auto (rarement utilisé).
72
Durée de vie des variables
• Variable globale : variable déclarée en dehors des
fonctions.
int n;
void fonction ();
void fonction () n est initialisée à 0 par le compilateur
appel numero 1
{
appel numero 2
n++;
appel numero 3
printf("appel numero %d\n",n); appel numero 4
return; appel numero 5
}
main()
{
int i;
for (i = 0; i < 5; i++)
fonction();
}
73
Durée de vie des variables
• Variable locale : variable déclarée à l'intérieur d'une
fonction (ou d'un bloc d'instruction).
int n = 10;
void fonction ();
void fonction () n est initialisée à 0 par le compilateur
{ appel numero 1
int n = 0; appel numero 1
n++; appel numero 1
printf("appel numero %d\n",n); appel numero 1
return; appel numero 1
}
main()
{
int i;
for (i = 0; i < 5; i++)
fonction();
}
74
Durée de vie des variables
• Il est possible de déclarer une variable locale de classe statique
qui reste locale à une fonction mais sa valeur est conservée
d'un appel au suivant : static type nom-de-
variable;
int n = 10; n est initialisée à 0 par le compilateur
void fonction (); appel numero 1
void fonction () appel numero 2
{ appel numero 3
static int n; appel numero 4
n++; appel numero 5
printf("appel numero %d\n",n);
return;
}
main()
{
int i;
for (i = 0; i < 5; i++)
fonction();
}
75
Transmission des paramètres d'une fonction
• Les paramètres de fonction sont traités de la même manière
que les variables locales de classe automatique. On dit que les
paramètres d'une fonction sont transmis par valeurs.
• Exemple :
void echange (int , int);
void echange (int a, int b)
{
int t;
printf("debut fonction :\n a = %d \t b = %d\n",a,b);
t = a;
a = b;
b = t;
printf("fin fonction : \n a = %d \t b = %d\n",a,b);
return;
}
76
Transmission des paramètres d'une fonction
main()
{
int a = 2, b = 5;
printf("debut programme principal : \n a = %d \t b = %d\n",a,b);
echange(a,b);
printf("fin programme principal : \n a = %d \t b = %d\n",a,b);
}
imprime
debut programme principal :
a=2 b=5
debut fonction :
a=2 b=5
fin fonction :
a=5 b=2
fin programme principal :
a=2 b=5
77
Transmission des paramètres d'une fonction
• Pour q'une fonction modifie la valeur de ses arguments, il faut il faut passer les
paramètres par adresse :
void echange (int *, int*);
void echange (int *adr_a, int *adr_b)
{
int t;
t = *adr_a;
*adr_a = *adr_b;
*adr_b = t;
return;
}
main()
{
int a = 2, b = 5;
printf("debut programme principal : \n a = %d \t b = %d\n",a,b);
echange(&a,&b);
printf("fin programme principal : \n a = %d \t b = %d\n",a,b);
}
78
La fonction main
• La fonction principale main est une fonction comme les autres.
Elle est souvent déclarée sans type mais l'option -Wall de gcc
provoque un message d'avertissement.
• En fait la fonction main est de type int dont la valeur est 0 si
l'exécution se passe bien différente de 0 sinon
• On peut utiliser deux constantes définies dans la librairie
stdlib.h :
– EXIT_SUCCESS = 0
– EXIT_FAILURE = 1
• En principe la fonction main sans arguments en pour prototype:
– int main(void)
79
La fonction main
• La fonction main peut également posséder des paramètres formels.
• En effet un programme C peut recevoir une liste d'arguments au
lancement de son exécution.
• La ligne de commande est dans ce cas là est composée du nom du
fichier exécutable suivi par des paramètres.
• main possède 2 paramètres formels appelés par convention :
– argc (argument count) : variable de type int fourni le nombre de mots
composant la ligne de commande y compris l'exécutable.
– argv (argument vector) : est un tableau de chaînes de caractères
correspondant chacune à un mot de la ligne de commande.
• argv[0] contient le nom du fichier exécutable
• argv[1] contient le premier paramètre
• …
int main (int argc, char *argv[]);
80
La fonction main
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char *argv[])
{
int a, b;
if (argc !=3)
{
printf("\nErreur : nombre invalide d'arguments");
printf("\nUsage : %s int int \n",argv[1]);
printf(EXIT_FAILURE);
}
a = atoi(argv[1]);
b = atoi(argv[2]);
printf("\nLe produit de %d par %d vaut : %d\n", a, b, a * b);
return(EXIT_SUCCESS);
}
On lance l'exécutable avec deux paramètres : a.out 12 8
81
Pointeur sur une fonction
• Le langage C offre la possibilité de passer une fonction comme
paramètre d'une autre fonction. On utilise un mécanisme de
pointeur
• un pointeur sur une fonction ayant pour prototype
type fonction (type-1,type-2,…,type-n);
est de type
type (*)(type-1,…,type-2);
• Ex :
int operateur_binaire(int a, int b, int (*f)(int, int));
– sa déclaration est donnée
int operateur_binaire(int , int , int (*)(int, int));
– pour appeler la fonction opérateur en utilisant la fonction somme de
prototype : int somme(int, int);
– on écrit : operateur_binaire(a, b, somme)
82
Pointeur sur une fonction
– Dans le corps de la fonction operateur_binaire on écrit (*f)(a,b) :
int operateur_binaire(int a, int b, int (*f)(int, int))
{
return((*f)(a,b));
}
83
Pointeur sur une fonction
#include<stdlib.h> int main(int argc, char *argv[]);
#include<stdio.h> { int a, b;
#include<string.h> if (argc !=4)
void usage(char *);
{ printf("\nErreur : nombre invalide d'arguments");
int somme(int, int);
int produit(int , int);
usage(argv[0]);
int operateur-binaire(int, int, int (*)(int, int)); return(EXIT_FAILURE);
void usage(char *cmd) }
{ a = atoi(argv[1]);
printf("\nUsage : %s int [plus|fois] int\n",cmd); b = atoi(argv[1]);
}
if (!strcmp(argv[2], "plus"));
int somme (int a, int b)
{
{ printf("%d\n",operateur_binaire(a,b,somme));
return(a + b); return(EXIT_SUCCESS);
} }
int produit (int a, int b) if (!strcmp(argv[2], "fois"));
{ { printf("%d\n",operateur_binaire(a,b,produit));
return(a * b);
return(EXIT_SUCCESS);
}
int operateur_binaire (int a, int b, int (*f)(int , int))
}
{ else
return((*f)(a, b)); { printf("\nErreur : argument(s) invalide(s)");
} usage(argv[0]);
return(EXIT_Failure);
}
} 84
La programmation modulaire
Principes élémentaires
• Nécessité de fractionner un programme C en
plusieurs fichiers sources, que l'on compile
séparément
• 3 règles d'écriture d'un programme C :
– l'abstraction des constantes littérales
– la factorisation du code
– la fragmentation du code
86
Principes élémentaires
• L'abstraction des constantes littérales : éviter
d'utiliser explicitement des constantes littérales
dans le corps, ceci rend les modifications et la
maintenance difficile :
– fopen("nom_fichier","r");
– perimetre = 2 * 3.14 * rayon;
– sauf le cas particulier des constantes symboliques au
moyen de la directive #define.
87
Principes élémentaires
• La factorisation du code : éviter de dupliquer du
code. Définition systématiquement des fonctions
(même de petite taille)
• la fragmentation du code : découpage d'un
programme en plusieurs fichiers pour plus de
lisibilité.
– Placer une partie du code dans un fichier en-tête (ayant
l'extension .h) que l'on inclut dans le programme
principale par la directive #include.
88
Principes élémentaires
/************************************************************/
/**** fichier : main.c ****/
/**** saisit 2 entiers et affiche leur produit ****/
/************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include "produit.h"
int main(void)
{
int a, b , c;
scanf("%d",&a);
scanf("%d",&b);
c = produit(a,b);
printf("\nle produit vaut %d\n",c);
return EXIT_FAILURE
}
/************************************************************/
/**** fichier : produit.h ****/
/**** produit de 2 entiers ****/
/************************************************************/
int produit (int , int)
int produit (int a, int b)
{
return(a * b);
}
• la compilation se fait séparément
89
La compilation séparée
• Si on reprend l'exemple précédent, on doit
compiler les fichiers séparément :
– gcc -c produit.c
– gcc -c main.c
– gcc main.o produit.o
– Si on compile avec l'option -Wall, à la compilation il y aura un
message de warning qui rappelle que la fonction produit n'a pas été
déclarée dans le main.
– On peut également faire une seule commande : gcc produit.c main.c
• Fichier en-tête d'un fichier source :
– à chaque fichier source nom.c un fichier en-tête nom.h comportant
les déclarations des fonctions non locales au fichier nom.c (ces
fonctions sont appelées fonctions d'interface) ainsi que les
définitions des constantes symboliques et des macros qui sont
partagées par les 2 fichiers.
90
La compilation séparée
91
La compilation séparée
/**********************************/ /******************************************/
/**** fichier : produit.h *****/ /**** fichier : main.c *****/
/**** en-tete de produit.c *****/ /**** saisit de 2 entiers et affiche leur produit****/
/*****************************************/
/*********************************/
#include<stdlib.h>
extern int produit (int , int); #include<stdio.h>
/*********************************/ #include "produit.h"
/**** fichier : produit.c *****/
/**** produit de 2 entiers *****/ int main (void)
{
/*********************************/
int a, b, c:
#include "produit.h" scanf("%d",&a);
int produit (int a, intb) scanf("%d",&b);
{ c = produit(a,b);
return(a *b); printf("\n le produit vaut %d\n",c);
return EXIT_SUCCESS;
}
}
92
La compilation séparée
• Pour éviter une double inclusion de fichier en-tête, il est
recommandé de définir une constante symbolique,
souvent appelée NOM_H au début du fichier nom.h dont
l'existence est précédemment testée.
• Si cette constante est définie alors le fichier nom.h a déjà
été inclus.
/**********************************/
/**** fichier : produit.h *****/
/**** en-tete de produit.c *****/
/*********************************/
#ifndef PRODUIT_H
#define PRODUIT_H
extern int produit (int , int);
#endif /* PRODUIT_H*/
93
La compilation séparée
• Les règles :
– à tout fichier source nom.c on associe un fichier en-tête nom.h qui
définit son interface
– le fichier nom.h se compose :
• déclarations des fonctions d'interface
• éventuelles définitions de constantes symboliques et de macros
• éventuelles directives au pré-processeur
– le fichier nom.c se compose :
• variables permanentes
• des fonctions d'interface dont la déclaration se trouve dans nom.h
• éventuelles fonctions locales à nom.c
– le fichier nom.h est inclus dan nom.c et dans tous les fichiers qui
font appel à une fonction définies dans nom.c
94
L'utilitaire make
• Plusieurs fichiers sources compilés séparément ⇒
compilation longue est fastidieuse ⇒ automatisation à
l'aide de make d'UNIX
• Principe de base :
– Avec make, effectuer uniquement les étapes de compilation
nécessaires à la création d'un exécutable.
– make recherche par défaut le fichier makefile ou Makefile dans le
répertoire courant.
– On peut utiliser un autre fichier dans ce cas lancer la commande
make avec l'option -f :
• make -f nom_fichier
95
Création d'un Makefile
Cible : liste de dépendances
<TAB> commande UNIX
• fichier cible ensuite la liste des fichiers dont il dépend
(séparés par des espaces)
• après <TAB> il y a les commandes (compilation) UNIX à
exécuter dans le cas où l'un des fichiers de dépendances
est plus récent que le fichier cible.
## Premier exemple de Makefile
prod : produit.c main.c produit.h
gcc -o prod produit.c main.c
• l'exécutable prod dépend des 2 fichiers produit.c, main.c et
l'en-tête produit.h (les commentaires sont précédés de #)
make prod
96
Création d'un Makefile
• Dans le premier exemple on n'utilise pas pleinement les
fonctionnalités de make
## Deuxieme exemple de Makefile
prod : produit.o main.o
gcc -o prod produit.o main.o
main.o : main.c produit.h
gcc -c main.c
produit.o : produit.c produit.h
gcc -c produit.c
• On peut rajouter, si on veut être bien organisé, une cible
appelée clean permettant de nettoyer le répertoire
courant :
– clean : rm -f prod *.o
97
Macros et abréviations
• Pour simplifier l'écriture d'un fichier Makefile, on peut
utiliser un certain nombre de macros sous la forme :
nom_de_macro = corps de la macro
• quand la commande make est exécutée, toutes les
instructions du type $(nom_de_macro) dans le Makefile
sont remplacées par le corps de la macro.
## Exemple de Makefile avec macros
CC = gcc
prod : produit.o main.o
$(CC) -o prod produit.o main.o
main.o : main.c produit.h
$(CC) -c main.c
produit.o : produit.c produit.h
$(CC) -c produit.c
98