ch3 Pointeurs (Mode de Compatibilité)

Télécharger au format pdf ou txt
Télécharger au format pdf ou txt
Vous êtes sur la page 1sur 98

Faculté des Sciences Tétouan

Département d’Informatique

La notion de
Type Abstrait de Données
(TDA)

Said EL GAROUANI

1
Les mémoires...

RAM (Random Access Memory): 4 / 8 Go

Le disque dur: 300 -1500 Go

La mémoire virtuelle: temps d’accès 1000 fois


plus long.

2
Mémoire et exécution

Code Code objet du programme

Données statiques Valeurs constantes

Pile
Piles d’appels de fonctions

Tas Allocation dynamique de


mémoire
3
Intérêts des pointeurs
Gestion de l’espace mé
émoire en cours d’exé
écution

Modifications de variables passé


ées en paramè
ètres
de fonction

Repré
ésentation de tableaux: accè
ès direct et indexé
é


éfé
érences croisé
ées

Fonctions virtuelles en programmation objet


4
Rappels sur les pointeurs

Int *a;
a

Déclaration d’un pointeur vers un entier

5
Rappels sur les pointeurs

Int *a; Int *a = NULL;


a

Déclaration d’un pointeur vers un entier


et initialisation à “NULL”
6
Rappels sur les pointeurs

malloc(3*sizeof(int));

Allocation dynamique de place mé


mémoire
(pour 3 entiers)

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

Allocation dynamique et assignment

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

But : paramétrer des fonctions par d’autres


fonctions pour modifier leur actions.

Déclaration :
type (* nom_de_fonction) ([arguments]);
Utilisation :
(* nom_de_fonction) (arg1, arg2, arg3,...);

11
Expemple de Pointeurs de fonction

short tab[10]

short carre( short v ) { return a * a; }


void imprimer(
imprimer(int nb_elems, short (* function)(short))
{
for( i = 0; i < nb_elems; ++i ) {
printf( “%d ”, (* function) ( tab[i] ) );
}
}

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

• Exemple: type de données Liste


13
Implantation d’un TDA
• Implantation ou implémentation :
traduction d’un TDA donné sous forme
algorithmique dans un langage de
programmation

• En langage C, on implantera un TDA sous


forme de module dans lequel chaque
opération est traduite par une fonction
14
Organisation de la mémoire
• La mémoire (RAM) est un immense tableau de
cases contenant chacune 1 octet (ou byte) = 8 bits
… …

• Une variable est stockée sur une ou plusieurs cases en


fonction de son type et de la machine. En général :
– Entiers « courts » (short int): 2 octets
– Entiers (int) et réels en simple précision (float): 4 octets
– Réels en double précision (double): 8 octets
⇒ Intervalle de valeurs possibles plus ou moins grand
15
Notion d’adresse
• L’adresse d’une variable correspond au numéro de
la première case stockant cette variable
Ex: entier à l’adresse 103 est en fait stocké dans les
cases 103, 104, 105 et 106

0 … 103 104 105 106 107 …


… …

• Le nombre d’adresses disponibles dépend


de la taille de la mémoire mesurée en Go
⇒ 109 octets ⇒ milliards de cases…
16
Opérateur &
• En langage C, l’adresse en mémoire d’une variable
est donnée par &variable
• Même principe dans le cas des tableaux :
– &(tb1[i]) correspond à l’adresse en mémoire de tb1[i]
&tb1[0] &tb1[1] &tb1[2]

tb1[0] tb1[1] …

– tb1 est en fait l’adresse en mémoire du


premier élément du tableau, c’est-à-dire à
&(tb1[0])
17
Arithmétique d’adresses dans les
tableaux
• Les adresses-mémoire du tableau sont contiguës et
toutes les valeurs sont stockées sur le même nombre
de cases
⇒ on peut utiliser les opérations arithmétiques + et –

• 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 ;

• Affectation de l’adresse d’un tableau :


int tb1[10], *p ;
p = tb1; /* p pointe sur la première case de tb1 */
ou
p = &(tb1[0]) ;

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 */

• Fonctionne aussi avec les adresses :


*(tab + 3) est équivalent à tab[3]

23
Notion de pointeur
int i = 3
int *p;
p = &i;
objet adresse valeur
i 431836000 3
p 431836004 431836000

Toute modification de *p modifie i.

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;
} }

après l'affectation de *p2 à *p1 après l'affectation de p2 à p1

objet adresse valeur objet adresse valeur


i 431836000 6 i 431836000 3
j 431836004 6 j 431836004 6
p1 431835984 431836000 p1 431835984 431836004
p2 4831835992 431836004 p2 4831835992 431836004

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

• Exemple : Définit un pointeur p sur un objet *p de type


int, et affecte à *p la valeur de la variable i.
#include<stdio.h>
résultat du programme :
#include<stdlib.h> valeur de p avant initialisation = 0
main() valeur de p après initialisation = 5368711424
valeur de *p = 3
{
int i = 3;
int *p;
printf("valeur de p avant initialisation = %ld\n",p);
p = (int*)malloc(sizeof(int));
printf("valeur de p après initialisation = %ld\n",p);
*p = i;
printf("valeur de *p = %d\n",*p);
}
30
Allocation dynamique
#include<stdio.h>
#include<stdlib.h>
main() Définit un pointeur p sur un objet *p de type int, réserve 2 places
d'objets de type int à l'adresse p (8 octets).
{ résultat du programme :
int i = 3; p = 5368711424 *p = 3 p+1 = 5368711428 *(p+1) = 6
int j = 6;
int *p;
p = (int*)malloc(2*sizeof(int));
*p = i;
*(P+1) = j;
printf("p = %ld \t *p = %d \t p+1 = %ld \t *(P+1) = %d
\n",p,*p,p+1,*(p+1));
}
31
Allocation dynamique

• La fonction calloc(nb-objets,taille-objets) joue


le même rôle que malloc sauf que celle-ci
initialise en plus l'objet pointé *p à 0
• Ex : p =(int*)calloc(N,sizeof(int));
⇔ p = (int*)malloc(N * sizeof(int));
for (i = 0; i < N; i++)
*(p+i) = 0;

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…

• Pour libérer une zone mémoire allouée


précédemment avec malloc
malloc, on utilise la
fonction free :
free(nom_du_pointeur
free( nom_du_pointeur)) ;

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

• Vu : chaîne de caractères est un tableau de


caractères terminé par \0
• On peut définir un pointeur sur un objet de type char
char *chaine;
• Affectation comme : chaine = "ceci est un texte";
• Et toute autres opérations valide sur les pointeurs.

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 ;

• Seul le premier champ peut être initialisé au moment de la


déclaration :
Contenu x = {10.5} ; /* Affecte la valeur 10 au champ
x.val_entier
x.val_entier */

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

• Type hétérogène composé de plusieurs champs pouvant avoir chacun leur


propre type

• Syntaxe :
struct nom_type {
type1 champ1 ;
type2 champ2 ;
type3 champ3 ;

};
• déclaration : struct nom_type objet;

• La taille réservée en mémoire est la somme des tailles des champs

45
Structures
• Ou :
struct nom_type {
type1 champ1 ;
type2 champ2 ;
type3 champ3 ;

} objet ;

• Accès à différents membres :


objet.champi le ième membre de l'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 ;

• Accès aux champs : utilisation du symbole .


printf("L’étudiant
printf ("L’étudiant %s %s a le numéro %d
%d\n", e.prenom
e.prenom,, e.nom,
e.INE) ;

e.INE = 12 ;

• Affectation possible entre deux variables de même type :


Etudiant e1, e2 ;

e1 = e2 ;
48
Structures
#include<math.h>
struct complexe{
double reelle;
double imaginaire;
};
main()
{
struct complexe z;
double norme;
norme=sqrt(z.reelle * z.reelle + z.imaginaire * z.imaginaire);
printf("norme de (%f + i %f) = %f \n",e.reelle,z.imagianire,norme);
}

49
Pointeurs sur des structures

• Comme pour les types de base, on peut utiliser des


« pointeurs sur structure » :
Etudiant e, *pe ;
pe = &e ;
⇒ pe contiendra l’adresse du premier champ de la structure

• Utilisation du symbole -> pour accéder aux champs dans le


cas des pointeurs :
pe
pe-->INE = 12 ;
Ou
(*pe).INE = 12 ;

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

• Une variable de type structure peut être


passée en entrée d’une fonction et/ou
renvoyée en sortie

• Mieux vaut passer par un pointeur sur la


structure si cette dernière contient beaucoup
de champs

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} } ;

• Pour l’accès aux champs, double indirection :


printf("L’étudiant
printf ("L’étudiant %s est né le %d / %d / %
%dd\n", e.nom,
e.date_naissance.jour
e.date_naissance.jour,, e.
e.date_naissance.mois
date_naissance.mois,,
e.date_naissance.annee
e.date_naissance.annee)) ;

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 ;
};

• La tête de la liste est un pointeur sur la première


cellule

56
Schéma du chaînage obtenu

Liste Adresse cell. 1 Adresse cell. 2 Adresse cell. 3 …


Adresse cell. 1 Contenu cell. 1 Contenu cell. 2 Contenu cell. 3
Adresse cell. 2 Adresse cell. 3 Adresse cell. 4

• La dernière cellule de la liste pointe sur l’adresse NULL


• En mémoire :

Adresse cell. 3 Adresse cell. 1 Liste Adresse cell. 2

… Contenu cell. Adresse


3 cell. 4 …
Contenu cell. Adresse
1 cell. 2 …Adresse cell. 1 …
Contenu cell. Adresse
2 cell. 3 …

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

• On ne sait pas a priori combien de cellules vont constituer


la liste
⇒ impossible de réserver l’espace mémoire nécessaire au début du
programme (comme avec la représentation par tableaux – allocation
statique)
⇒ il faut réserver l’espace nécessaire pendant l’exécution : à chaque
fois que l’on ajoute une nouvelle cellule, on alloue l’espace pour cette
cellule (allocation dynamique – fonction malloc)

• Intérêt : seul l’espace réellement nécessaire est réservé

59
Directives

• Préprocesseur est un programme exécuté lors de


la première compilation
• il effectue des modifications textuelles sur le
fichier source à partir de directives :
– incorporation de fichiers source (#include)
– définition de constantes symboliques et de macros
(#define)
– compilation conditionnelle (#if, #ifdef, …)

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

• On peut remplacer l'instruction #define DEBUG au moment


de la compilation par gcc -DDEBUG fichier.c

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

Déclaration d'une fonction


• C n'autorise pas les fonctions imbriquées.
• On peut déclarer une fonction secondaire soit avant, soit
après la fonction principale main.
• Toutefois, il est indispensable que le compilateur
"connaisse" la fonction à son appel. Elle doit
impérativement être déclarée avant :
– type nom-fonction ( type-1, …, type-n);

70
Déclaration d'une fonction
int puissance (int , int)p;

int puissance (int a, int b)


{
if (n == 0)
return(1)
return(a * puissance(a, n-1));
}

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

• Fichier en-tête d'un fichier source :


– le fichier en-tête nom.h doit être inclus par la directive #include dans
tous les fichiers sources qui utilisent une des fonctions définies dans
nom.c.
– il faut faire, par ailleurs, précéder la déclaration de la fonction du
mot-clef extern, qui signifie que cette fonction est définie dans un
autre fichier.
– Exemple :

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;
}
}

gcc produit.c main.c

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

Vous aimerez peut-être aussi