0% ont trouvé ce document utile (0 vote)
21 vues98 pages

Chap 1 C 2

Le module de Programmation C2 vise à enseigner l'utilisation des pointeurs, l'allocation dynamique de mémoire, les structures, la récursivité, ainsi que les techniques de tri et de recherche dans les tableaux. Les étudiants apprendront également à manipuler des fichiers en C. Le cours est structuré en plusieurs chapitres, chacun abordant des concepts fondamentaux et des techniques de programmation essentielles.

Transféré par

Abd Essamad
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
21 vues98 pages

Chap 1 C 2

Le module de Programmation C2 vise à enseigner l'utilisation des pointeurs, l'allocation dynamique de mémoire, les structures, la récursivité, ainsi que les techniques de tri et de recherche dans les tableaux. Les étudiants apprendront également à manipuler des fichiers en C. Le cours est structuré en plusieurs chapitres, chacun abordant des concepts fondamentaux et des techniques de programmation essentielles.

Transféré par

Abd Essamad
Copyright
© © All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Université Mohammed Premier

Faculté des Sciences - Oujda

Filière : Informatique Appliquée


Semestre 2

Module

Programmation C2
1
noura.ouerdi2@[Link]
Département d’Informatique
A.U. 2023-2024
OBJECTIFS DU MODULE

✓ Comprendre et utiliser les pointeurs et l’allocation dynamique de


la mémoire.
✓ Représenter des données sous forme de structures.
✓ Implémenter récursivement des fonctions.
✓ Déterminer la complexité des algorithmes.
✓ Savoir trier un tableau
✓ Chercher dans un tableau
✓ Stocker et traiter des données de fichiers

2
CONNAISSANCES À
ACQUÉRIR

▪ Avoir des connaissances en matière de :

✓ Pointeurs et allocation dynamique de la mémoire


✓ Structures
✓ Récursivité
✓ Tri et recherche dans les tableaux
✓ Fichiers

3
PLAN DU COURS
▪ Chapitre 1 : Pointeurs et allocation dynamique de la
mémoire
▪ Chapitre 2: Structures
▪ Définition des types structures :
▪ Définition par type struct
▪ Définition par typedef
▪ Déclaration des variables structures
▪ Accès aux champs d’une structure
▪ Chapitre 3 : Récursivité
▪ Récursivité simple
▪ Récursivité multiple

4
PLAN DU COURS
▪ Chapitre 5 : Tri et recherche
▪ Tri par sélection
▪ Tris à bulle
▪ Tri par fusion
▪ Recherche séquentielle
▪ Recherche dichotomique
▪ Chapitre 6 : Les fichiers
▪ Introduction
▪ Déclaration de fichiers
▪ Fonctions d’ouverture et de fermeture (fopen, fclose)
▪ Fonction de lectures et d’écriture (fread, fwrite)
5
CHAPITRE 1 : POINTEUR ET
GESTION DYNAMIQUE

6
I – Les Pointeurs

II – La gestion dynamique de la mémoire


INTRODUCTION
Rappel : structure d’un programme C
◼ La conception et l’exécution d’un programme par le langage C passe
par trois étapes :
a) Édition d’un programme source à l’aide d’un éditeur de texte :
- Cette phase permet donc de créer un fichier texte qui sera sauvegardé
avec l’extension ".c " « NomduFichier.c ».
b) Compilation du programme source à l’aide d’un compilateur :
- Le compilateur génère un fichier binaire appelé fichier objet
« [Link] ».
- Puis génère par la suite le programme exécutable portant le même le
nom que le programme source mais avec une extension différente qui
est ".exe" .
- Ce programme exécutable n’est autre que la traduction en langage
machine du programme source.
c) Exécution du programme exécutable :
- Cela permet d’exécuter l’ensemble des instructions du programme source

7
Introduction
Rappel : Étapes de compilation
Code source programme.c

Compilateur Pour le compilateur que


nous utiliserons, différents
types de fichiers seront
Oui identifiés par leurs
Erreur de
syntaxe? extensions:
.c : fichier source.
Non .obj : fichier compilé.
Code objet [Link] .exe : fichier exécutable.
.h : bibliothèque en-tête.

Editeur de
liens

Programme exécutable
[Link]
8
Introduction
Rappel : Structure d’un programme C
Fichier entête contient ce dont le compilateur a
besoin pour compiler correctement le programme

Fonction principale <directives de compilation> Liste exhaustive des


variables utilisées
dans la fonction
Accolade ouvrante int main () {
marquant le début
Traitements réalisés
de la fonction Déclaration des variables par la fonction
Corps Termine l’exécution
Accolade fermante
marquant la fin de du programme
la fonction return EXIT_SUCCESS;
} Le nom du fichier
monProgramme.c contenant le
programme

9
Notion de Variable - RAM
RAM

RAM . int X;
Données
. .
.
Variables
Constantes
. X = 25;

Instructions
. (00011001)2
(19)16 = 0x19
Lectures 0x0004
Affectations
Tests 0x00 0x0003

croissantes
Adresses
Boucles
0x00 0x0002

X
Ecritures 0x00 0x0001

0x19 0x0000

10
DONNÉES STATIQUES ET DYNAMIQUES
▪ En langage C un programme comporte deux types
de données :

▪ Statiques ;
▪ Automatiques;
▪ Dynamiques.

11
DONNÉES STATIQUES
▪ Les données statiques occupent un
emplacement parfaitement défini lors de la
compilation. La mémoire allouée est fixe.
▪ Une fois la mémoire allouée, elle ne peut plus
être changée. Par exemple, en langage C si le
programmeur écrit int x[10]; x est un tableau
pouvant stocker une séquence de données du
même type. Il peut stocker dix éléments
entiers. Il ne peut pas stocker plus de dix
éléments.

12
DONNÉES AUTOMATIQUES
▪ Elles, en revanche, n’ont pas une taille
définie a priori.
▪ En effet, elles ne sont créées et détruites qu’au
fur et à mesure de l’exécution du programme.
▪ Elles sont souvent gérées sous forme de ce
que l’on nomme une pile (stack en anglais).
Remarque : Dans la plupart des langages de
programmation compilés, la pile (stack) est
l’endroit où sont stockés les paramètres
d’appel et les variables locales des
fonctions (ces variables sont désallouées
automatiquement)
13
DONNÉES DYNAMIQUES

▪ Les données dynamiques n’ont pas non plus


de taille définie.
▪ Leur création ou leur libération dépend,
cette fois, de demandes explicites faites lors
de l’exécution du programme.
▪ En fonction d’ajout et de suppression, la
mémoire peut être augmentée ou diminuée.
C’est ce qu’on appelle l’allocation
dynamique de mémoire.
14
POINTEURS
▪ Les variables, déclarées dans un programme,
sont placées en mémoire.
▪ On peut se demander où exactement ces
variables sont stockées ?
▪ Sont elles placées les unes à la suite des
autres ou bien sont dispersées dans la
mémoire?

15
POINTEURS
Exemple 1 :
main()
{ int a,b,c;
printf("Taille d'un entier%d\n",sizeof(int));
printf("Adresse de a %x\n",&a);
printf("Adresse de b %x\n",&b);
printf("Adresse de c %x",&c);
getch(); }
%x pour un affichage en Hexadécimal
16
POINTEURS

17
POINTEURS (SUITE)

Ce programme crée trois int en mémoire, affiche


la taille d’un int, et affiche les adresses
respectives des trois variables en hexadécimal.

L’opérateur & permet de connaître


! l’adresse en mémoire d’une variable et
sizeof(int) donne la taille d’un entier.

18
DÉFINITION D’UN POINTEUR

▪Un pointeur est une variable


qui contient l’adresse d’une
autre variable.

19
DÉCLARATION D’UN POINTEUR
La syntaxe pour déclarer un pointeur est la suivante :

Type *NomPointeur ;

Type : désigne un type de variable dont le pointeur


peut contenir l’adresse.

NomPointeur : désigne le nom du pointeur.

Le symbole ’*’ (astérisque) doit être placer entre le


type et le nom du pointeur.
20
INTÉRÊTS DES POINTEURS
◼ Les pointeurs présentent de nombreux avantages :
▪ Le passage de paramètres par référence (par adresse) à des
fonctions.
▪ Les tableaux ne permettent de stocker qu’un nombre fixé
d’éléments de même type. Si les composantes du tableau sont
des pointeurs, il sera possible de stocker des éléments de tailles
diverses.
▪ La manipulation des structures de données (listes, arbres, files,
piles,…) dont le nombre d’éléments peut évoluer
dynamiquement. Ces structures sont très utilisées en
programmation.
▪ Ils permettent d'écrire des programmes plus compacts et
efficaces.

21
DÉCLARATION D’UN POINTEUR (SUITE)
◼ Dans la déclaration d’un pointeur, le symbole * est associé au nom
de la variable pointeur et non pas au type.
◼ Ainsi, la déclaration :
int *a, b ;
Ne déclare pas deux pointeurs a et b, mais un pointeur a vers un
int et une variable simple b de type int.
◼ Pour déclarer deux pointeurs a et b dans la même ligne, il faut
écrire :
int *a , *b ;
◼ La valeur d’un pointeur donne l’adresse du premier octet parmi
les n octets où la variable est stockée.

22
DÉCLARATION D’UN POINTEUR (SUITE)
◼ Lors du travail avec des pointeurs, nous avons besoin :
- d’un opérateur 'adresse de’ & pour obtenir l'adresse d'une variable.
- d’un opérateur 'contenu de' * pour accéder au contenu d'une
adresse.
◼ Lorsqu’on déclare un pointeur p sur un objet de type T , on ne sait
pas sur quoi il pointe.
◼ Pour initialiser un pointeur de type T, il faut lui affecter l’adresse
d’une variable de même type T.

23
DÉCLARATION D’UN POINTEUR (SUITE)
Exemple 2 :
int *ad ;
int n ;
n = 20 ;
ad = &n ;
*ad = 30;
int *ad ; réserve la variable ad comme étant un “pointeur”
sur des entiers.

ad = &n; affecte à la variable ad la valeur de l’expression


&n. L’opérateur & fournit comme résultat l’adresse de
son opérande. Donc, l’instruction ad = &n; place dans la
variable ad l’adresse de la variable n.
24
DÉCLARATION D’UN POINTEUR (SUITE)
On peut schématiser ainsi la situation au début de la déclaration
comme suit :
mémoire int n ;

n int *ad ;
20 30
n = 20 ;
n = 30
ad = &n ;
ad
*ad=30 ; affecte la valeur 30 à *ad,
c’est à dire n = 30 ;

25
DÉCLARATION D’UN POINTEUR (SUITE)
mémoire int *ad1 , *ad2 ;

1022 n

20 p
int n = 10 ;
ad1 int p = 20 ;
ad1 = &n ;
ad2 = &p ;
ad2
*ad1 = *ad2+2;

26
les instructions 3 et 4 placent respectivement dans ad1 et ad2 les adresses de n et p.
DÉCLARATION D’UN POINTEUR (SUITE)

En réalité, vous trouvez toutes sortes d’écriture pour

!
déclarer un pointeur.
int* p ;
int *q ;
int * r ;

Exemple 4 :
main()
{ int a ; int * p ; a = 12 ; p = &a ; *p = 15 ;
printf(‘’ a : %d \n ‘’,a) ; printf(‘’ *p : %d \n’’,*p) ;
getch() ;}

Résultat : a : 15 *p : 15

27
DÉCLARATION D’UN POINTEUR (SUITE)

Attention : int* p, q ;

! ne déclare pas deux pointeurs, mais


p un pointeur vers un int et
q une variable int.

28
ACCÈS À LA VARIABLE POINTÉE
◼ L’opérateur * permet d’accéder à la valeur de la variable pointée par un
pointeur donné.
◼ Considérons un pointeur p qui pointe vers une variable w de type T.
L’expression *p est équivalente à w.
◼ Donc, on peut manipuler la variable w soit directement par son nom ou soit
indirectement par *p.
◼ En fait, *p est considérée comme une variable de type T et tout ce qui est
applicable sur w, on peut l’appliquer sur *p.
◼ Exemple:
◼ Si un pointeur p pointe sur une variable a, alors toutes les opérations réalisées
sur la variable a (accès direct) peuvent être réalisées sur *p (accès indirect) .
int a = 3 , b , *p ;
p = &a;
b = a – 1; /* est équivalente à b = *p - 1 */
a += 2 ; /* est équivalente à *p += 2 */
++a ; /* est équivalente à ++*p */
a++; /* est équivalente à (*p)++ */

29
ACCÈS À LA VARIABLE POINTÉE
#include <stdio.h>
main(){
int n = 2, m = -4, k;
int *p, *q;
p=&n;
q=&m;
K =*p * *q;
printf("%d \t %d \t %d",*p,*q,k);
(*p)-- ;
(*q)++ ;
printf("%d \t %d \t %d",*p,*q, n);
(*p)+=4 ;
(*q)-=2 ;
printf("%d \t %d \t %d",*p,*q, m);}
Résultat?
2 -4 -8
1 -3 1
5 -5 -5 30
ACCÈS À LA VARIABLE POINTÉE
◼ Remarque : Si un pointeur P pointe sur une variable X, alors
*P peut être utilisé partout où on peut écrire X.
X+=2 équivaut à (*P)+=2
++X équivaut à ++*P
X++ équivaut à (*P)++ // != *P++

▪ Les parenthèses ici sont obligatoires car l’associativité des


opérateurs unaires * et ++ est de droite à gauche.

31
INITIALISATION D’UN POINTEUR
◼ Toute utilisation d’un pointeur devra être précédée par une initialisation en
utilisant l’opérateur l’opérateur unaire "&" .
◼ A la déclaration d’un pointeur p, on ne sait pas sur quelle zone mémoire il
pointe.
◼ Ces instructions peuvent générer des problèmes :
int *p;
*p = 10; // *p n’a pas de sens, elle n’existe pas
provoque un problème mémoire car le pointeur p n’a pas été initialisé.
Aussi, l’écriture suivante provoque un problème mémoire :
int *p;
p = 200; // ou p = 231564
◼ On ne peut pas le faire, car on ne sait pas si l’adresse 200 (ou l’adresse 231564)
est occupée par une autre variable du programme ou d’un autre programme,
voir un programme système.
◼ Il faut laisser le compilateur choisir les adresses convenables, c’est l’allocation
dynamique.

32
INITIALISATION D’UN POINTEUR
◼ On peut initialiser un pointeur en lui affectant :
▪ L’adresse d’une autre variable :
- Si la variable n’est pas un pointeur on doit utiliser l’opérateur &.
- Exemple :
int *p1; // déclaration d’un pointeur vers un int
int a=14; // supposons que a se trouve à l’adresse 0x352C
p1=&a; // affectation de l’adresse de a au pointeur p1, soit
0x352C
▪ Un autre pointeur déjà initialisé :
- L’affectation d’un pointeur à un autre est possible si les deux pointeurs sont de
même type.
- Si p1 et p2 sont deux pointeurs de type T, alors l’expression p1 = p2; permet
d’affecter le pointeur p2 au pointeur p1.
- Cela a pour effet de faire pointer p1 et p2 vers la même variable (la même
adresse).
- Exemple :
int *p2;
p2 = p1; //affectation de p1 à p2

33
INITIALISATION D’UN POINTEUR

▪ Affectation de p à la valeur NULL :


- On peut dire qu’un pointeur ne pointe sur rien en lui affectant la
valeur NULL.
- Cette valeur est définie dans <stddef.h> et <stdio.h>.
- Exemple :
int *p ;
p = NULL; // ou int *p = NULL;
On dit que p pointe « nulle part » : aucune adresse mémoire ne lui
est associé.

34
INITIALISATION D’UN POINTEUR

◼ Exemple 1 :

int *p, b=3; p=&b;


printf(" %d " , b); /* affiche 3 */
printf (" %d " , *p); /* *p existe et affiche 3 */
// *p est la valeur se trouvant dans l’adresse de b

35
INITIALISATION D’UN POINTEUR
◼ Exemple 2 :
int *p , a=5 , b=3 ;
p=&a; /* p pointe sur la variable a */
b=*p; /* affecte à la variable b la valeur pointée par p */
*p = 1; /* affecte la valeur 1 à la variable pointée par p */
printf (" %d ", b); /* affiche 5 */
printf (" %d ", *p); /* affiche 1 */
printf (" %d ", a); /* affiche 1 */

Question : Pour chaque instruction, donnez les valeurs


de b, *p et a (Mettre le tableau d’execution pas à pas)

a b *p
p=&a 5 3 5
b=*p 5 5 5
*p=1 1 5 1
36
INITIALISATION D’UN POINTEUR
◼ Exemple 3 :
int *p1, *p2; //déclaration de 2 pointeurs vers des entiers
int i = 14; // supposons que i se trouve à l’adresse 0x352C
p1 = &i; // affectation à l’adresse de i à p1 , soit 0x352C
p2 = p1; // affectation de p1 à p2 : p2 contient aussi
l’adresse de i
◼ Pour bien montrer l’intérêt de l’initialisation de tout pointeur, reprenons le
même exemple précédent dans lequel on remplace l’affectation p2 = p1 par
*p2 = *p1. Que va-t-il passer ?
◼ Réponse :
1. p1 est bien initialisé et pointe sur i (*p1=14).
2. Mais p2 n’a pas été initialisé :
▪ p2 désigne une adresse mémoire inconnue.
▪ L’instruction *p2 = *p1 force l’écriture de la valeur *p1=14 dans la case
mémoire pointée par p2, alors que p2 ne pointe sur aucune adresse valide.
Cela pourra entrainer un comportement indéfinie.
▪ Cette écriture est en général sanctionnée par le message d’erreur
37
”Segmentation fault” à l’exécution.
PASSAGE DE L’ADRESSE D’UNE VARIABLE DANS UNE
FONCTION
Nous voulons échanger simplement les valeurs de deux variables, à
l’aide d’une fonction. Voyons l’exemple suivant :
Swap(int a , int b) { int temp;
printf(“Avant l’échange dans la fonction Swap \n”);
printf(“a=%d et b=%d\n “,a,b) ;
temp = a; a=b; b = temp;
printf(“Après l’échange dans la fonction Swap \n”);
printf(“a=%d et b=%d\n “,a,b) ; }
main() { int x=10 , y=15 ;
printf(“Avant l’échange dans le programme principal \n”);
printf(“x=%d et y=%d \n”,x,y); Swap(x,y) ;
printf(“Après l’échange dans le programme principal \n”);
printf(“ x= %d et y=%d \n “,x,y) ; getch() ; } 38
PASSAGE DE L’ADRESSE D’UNE VARIABLE DANS UNE
FONCTION (SUITE)

L’échange des valeurs se fait uniquement


sur la fonction Swap et par conséquent, cet
échange n’a pas d’influence sur le
programme principal.
Pour remédier au problème, on va utiliser
les pointeurs (Passage par adresse)
comme dans l’exemple de la diapo
suivante :
39
PASSAGE DE L’ADRESSE D’UNE VARIABLE DANS UNE
FONCTION (SUITE)

Swap(int* a , int* b) { int temp;


printf(“Avant l’échange dans la fonction Swap \n”);
printf(“a=%d et b=%d\n “,*a,*b) ;
temp = *a; *a = *b ; *b = temp;
printf(“Après l’échange dans la fonction Swap \n”);
printf(“a=%d et b=%d\n “,*a,*b) ; }

main() { int x=10 , y=15 ;


printf(“Avant l’échange dans le programme principal \n”);
printf(“x= %d et y=%d \n”,x,y); Swap(&x,&y) ;
printf(“Après l’échange dans le programme principal \n”);
printf(“ x= %d et y=%d \n “,x,y) ; getch() ; }
40
RELATION ENTRE POINTEURS ET TABLEAUX
En C, l’identificateur d’un tableau, lorsqu’il est employé
seul (sans indice à sa suite) désigne en fait « l’adresse
de début » de ce tableau, c’est à dire l’adresse de son
premier élément.
Exemple 1 :

int tab[10] ; int *pa ; pa = &tab[0] ;

La notation tab est alors équivalente à &tab[0], de plus


tab est considéré comme étant de type pointeur sur le
type correspondant aux éléments du tableau (c’est à
dire : ici int).

En réalité, on a : tab = = &tab[0] ; 41


RELATION ENTRE POINTEURS ET TABLEAUX (SUITE)

Lorsqu’on déclare un tableau tab ( type tab[ ] ),


! tab est un pointeur vers le type déclaré.

En langage C, le nom d’un tableau correspond à


 son adresse, c’est à dire l’adresse du premier
élément du tableau.

42
Relation entre pointeurs et tableaux (suite)
▪ Version 1:
▪ main(){
float T[100], *pt;
int i, n;
do{ // test pour que 0 <= n <= 100
printf("Entrez n : \n ");
scanf("%d",&n);
} while(n<0 ||n>100);
pt=T;
for(i=0; i<n; i++){
printf("Entrez T[%d] \n ",i);
scanf("%f", pt+i); // pt + i  &T[i]
}
for(i=0;i<n;i++)
43
printf("%f \t",*(pt+i)); } // T[i]
Relation entre pointeurs et tableaux (suite)
▪ Version 2 : Sans utiliser i
main() {
Float T[100], *pt;
int n;
do{ // test pour que 0 <= n <= 100
printf("Entrez n : \n ");
scanf("%d",&n);
} while(n<0 ||n>100);
for(pt=T; pt<T+n; pt++){
printf ("Entrez T[%d] \n ",pt-T); // pour afficher 0,
1,..
scanf("%f", pt); //pt  &T[i]
}
for(pt=T; pt<T+n; pt++)
printf (" %f \t",*pt); } 44
Relation entre pointeurs et tableaux (suite)
◼ Exemple 1 :
Quelle est la valeur de la variable s après l’exécution de ce programme :
#include<stdio.h>
main(){
int x[6] = {1, 2, 3, 4, 5, 6}, i, s = 0;
int * p = &x[0]; // ou int *p=x
for(i = 0; i  6; i++){
s + = * (p + i);
} }
Résultat?
◼ Les variables utilisées : x est un tableau de 6 entiers, k et s deux entiers et p est
un pointeur vers un entier. La valeur initiale de s est 0.
◼ Le pointeur p va parcourir le tableau x en utilisant le pointeur p et calcule dans
s la somme des éléments de x par l’instruction :
s = s + *(p + i) 45
Relation entre pointeurs et tableaux (suite)

◼ Exemple 2:
#include <stdio.h>
main(){
int t[8]={-2,6,3,-2,1,9,0,-1};
int *p;
for(p=t; p<t+8; p++)
printf("%d \t", *p);
}
Résultat?
/* cette instruction va afficher : -2 6 3 -2 1 9
0 -1 */
46
Relation entre pointeurs et tableaux (suite)
◼ Exemple 3:
#include <stdio.h>
#define N 10
main(){
int T[N] = {3,6,0,8,97,0,5,6,0,8} ;
int *Deb, *Fin, *Comp;
Deb = &T[0] ;
Fin = &T[N-1];
for(Comp=Deb; Comp <= Fin; Comp++)
printf("%d \n",*Comp);}

Résultat?
// il affiche le tableau T 47
ARITHMÉTIQUE DES POINTEURS
On peut faire des calculs avec les pointeurs, mais les règles sont un
peu différentes. Un pointeur contient une adresse, qui exprime un
décalage, en octets, depuis l’adresse 0.

Exemple :
main() { int n=10, *p=&n;
printf("n est un int, de taille %d octets\n",sizeof(n));
printf("adresse de n : %p \n",p);
printf("adresse suivante : %p\n",++p); getch(); }

Vous allez avoir un résultat similaire à :

n est un int, de taille 4 octets


adresse de n : 0060FF24
adresse suivante : 0060FF28 48
ARITHMÉTIQUE DES POINTEURS
(SUITE)
▪ Certainement, vous n’aurez pas les mêmes

valeurs, mais vous allez constater que la


différence entre l’adresse suivante et l’adresse
de n représente un décalage du nombre d’octets
occupé par un int (n dans notre exemple).

▪ Pourtant, nous avons incrémenté le pointeur p de

1 (++p).
49
ARITHMÉTIQUE DES POINTEURS (SUITE)

▪ En réalité, lorsqu’on travaille avec les pointeurs,

l’unité ne vaut plus 1, mais la taille de l’objet vers


lequel on pointe (4 octets dans notre exemple).

▪ Donc ++p ( i.e : p = p + 1) incrémente de


1*sizeof(int) = 4 l’adresse contenu dans p.

50
ARITHMÉTIQUE DES POINTEURS (SUITE)
▪ Autre Exemple:

▪ main() { int n=10, *q, *p=&n;

▪ printf("n est un int, de taille %d octets\n",sizeof(n));

▪ q=p+1;

▪ printf("valeur de p: %d \n",p);

▪ printf("valeur de q : %d\n",q);

▪ printf("valeur de q-p : %d\n",q-p);

▪ getch(); }
51
ARITHMÉTIQUE DES POINTEURS (SUITE)
▪ Résultat:

52
ARITHMÉTIQUE DES POINTEURS (SUITE)
Dans le cas des tableaux, regardez cet exemple :
main() { int tab[10]; tab[3]=20;
printf("tab[3] vaut %d\n",tab[3]);
printf("tab[3] vaut %d\n",*(tab+3)); getch(); }
La première façon affiche la valeur de tab[3], c’est la façon
classique.

La seconde est une façon détournée : *(tab + 3) signifie la valeur


contenue à ( l’adresse tab plus un décalage de 3 fois le nombre
d’octets occupés par un int).

Donc, la valeur entre parenthèse représente un décalage en


53
mémoire.
OPÉRATIONS SUR LES POINTEURS
◼ Addition d’un entier à un pointeur :
▪ Si p est un pointeur vers une variable a de type T et k un entier, alors
l’expression p + k ou k + p est un pointeur vers la kème variable de type T
après la variable a.

p • a

p+3 •

54
OPÉRATIONS SUR LES POINTEURS

◼ Exemple :
Soit a une variable de type int et p est un pointeur vers a. Sachant que
le type int est codé sur 4 octets et que l’adresse de la variable a est
1030, quel est le contenu du pointeur p + 3 ?
◼ Solution :
▪ Le pointeur p pointe vers a, donc le contenu de p est l’adresse de a ,
c’est-à-dire 1030.
▪ Le pointeur p + 3 pointe vers l’entier qui se trouve 3 fois après celui
qui contient la variable a.
▪ Donc le contenu du pointeur p + 3 est 1030 + 3 * 4 = 1042.

55
OPÉRATIONS SUR LES POINTEURS
◼ Autre exemple :

#include <stdio.h>
main(){
double i = 3;
double *p, *q, *r;
p = &i;
q = p + 1;
r = p + 3 ;
printf("p = %d \t q = %d \t r = %d \n", p, q, r); }

▪ Il affiche : p = 2686784 q = 2686792


r = 2686808

56
SOUSTRACTION D’UN ENTIER À UN POINTEUR

• a

57
SOUSTRACTION D’UN ENTIER À UN POINTEUR

58
SOUSTRACTION D’UN ENTIER À UN POINTEUR
◼ Exemple :
#include <stdio.h>
main(){
int i = -1;
int *p, *q, *r;
p = &i;
q = p - 1;
r = p - 3 ;
printf("p = %d \t q = %d \t r = %d \n",p,q,r);
getch();
}

▪ Il affiche
p=2686788 q=2686784 r=2686776

59
INCRÉMENTATION D’UN POINTEUR

60
INCRÉMENTATION D’UN POINTEUR
◼ Exemple :
▪ La variable a de type int est rangée en mémoire à l’adresse 1000.
Quel est le contenu de chacun des pointeurs suivants :
int * p = &a;
int * q = p++;
int * r = ++q;
▪ Donnez les valeurs des différents pointeurs pour chaque instruction.
▪ Réponse?
▪ Le pointeur p pointe vers a, donc le contenu de p est l’adresse de a,
c’est-à-dire 1000.
▪ q = p++; est équivalente à q = p; p = p + 1; Donc le contenu du
pointeur q est 1000, mais le pointeur p est incrémenté et par suite, le
contenu de p est 1004.
▪ r = ++q; est équivalente à q = q + 1; r = q; Donc le contenu du
pointeur q est 1004, et celui de r est 1004.
▪ La variable a ?? Elle n’est plus pointée par ces pointeurs
61
DÉCRÉMENTATION D’UN POINTEUR
◼ Si p est un pointeur vers une variable a de type T.
L’expression p-- est le pointeur qui pointe vers la
variable de même type que a qui se trouve
immédiatement avant a.
◼ L’expression p-- est équivalente à l’expression
p = p - 1.

62
DÉCRÉMENTATION D’UN POINTEUR
◼ Exemple :
▪ La variable a de type int est rangée en mémoire à l’adresse 1000. Quel est le contenu
de chacun des pointeurs suivants :
int * p = &a;
int * q = p--;
int * r = --q;
Valeurs après exécution?
▪ Le pointeur p pointe vers a, donc le contenu de p est l’adresse de a, c’est-à-dire
1000.
▪ q = p--; est équivalente à q = p; p = p - 1; Donc le contenu du pointeur q est 1000,
mais le pointeur p est décrémenté et par suite, le contenu de p est 996.
▪ r = --q; est équivalente à q = q - 1; r = q; Donc le contenu du pointeur q est 996, et
celui de r est 996.
▪ r = ++q; est équivalente à q = q + 1; r = q; Donc le contenu du pointeur q est 1000,
et celui de r est 1000.
63
DIFFÉRENCE DE DEUX POINTEURS

64
DIFFÉRENCE DE DEUX POINTEURS

65
DIFFÉRENCE DE DEUX POINTEURS
◼ Remarque :
▪ La somme de deux pointeurs n’est pas autorisée.
▪ Produit et division aussi.
▪ ils n’ont aucun sens.
◼ Exemple :
#include <stdio.h>
main() {
int a;
int *p, *q;
p=&a ;
q=p+3;
printf("%d", q-p); /* cette instruction va afficher 3 */
printf("%d", p-q); /* cette instruction va afficher -3 */
}
66
POINTEURS DE TYPE POINTEURS
◼ Puisque les pointeurs sont des variables, et on peut déclarer des
pointeurs qui pointent sur n’importe quel type de variables, alors il
est possible de déclarer des pointeurs qui pointent sur des
pointeurs.
◼ La syntaxe de la déclaration est la suivante :

Type **Nom_pointeur ;

Type : désigne le type du pointeur pointée par le pointeur.

Nom_pointeur : est le nom du pointeur.

67
POINTEUR SUR UN POINTEUR
main() {
int a = 14 ; int* p = &a ; int** pp = &p ;
printf("a vaut : (%d , %d , %d)\n",a,*p,**pp);
printf("p vaut : (%p , %p , %p)\n",&a,p,*pp);
printf("pp vaut : (%p , %p)\n",&p,pp);
getch();
}

68
POINTEUR SUR UN POINTEUR
L’indirection représentée par deux
étoiles « ** », nous permet de pointer
sur un pointeur.
Pendant l’exécution de ce programme,
vous allez remarquer que sur chaque
ligne d’affichage, on voit plusieurs fois
la même valeur.

69
POINTEUR SUR UN POINTEUR

70
POINTEURS DE TYPE POINTEURS
◼ Exemple : float **p ;
Cette instruction déclare un pointeur qui pointe sur des pointeurs de
type réel. Ce concept est important pour les tableaux à deux
dimensions comme nous le verrons après.

71
POINTEURS DE TYPE POINTEURS
◼ En déclarant un tableau A[N][M] et un pointeur P du même type, on peut
manipuler le tableau A en utilisant le pointeur P en faisant pointer P sur le
premier élément de A (P=&A[0][0]), car la matrice A peut être considérée
comme un vecteur. Ainsi :
P pointe sur A[0][0] et *P désigne A[0][0]

P+1 pointe sur A[0][1] et *(P+1) désigne A[0][1] ….

P+M pointe sur A[1][0] et *(P+M) désigne A[1][0] …. // 2ème ligne

P+i*M pointe sur A[ i][0] et *(P+i*M) désigne A[i][0] …. // ième ligne

P+i*M+j pointe sur A[ i][ j] et *(P+i*M+j) désigne A[i][j]

◼ Ici la matrice A est considérée comme un tableau à 1 dimension, on peut la


représenter par un pointeur simple.
72
POINTEURS DE TYPE POINTEURS
float A[N][M];
◼ Cette instruction est interprétée par le compilateur comme étant une
déclaration d’un tableau à N lignes et M colonnes.
◼ Chaque ligne de ce tableau est considérée comme un tableau à une
dimension.
◼ La ième ligne du tableau peut être notée A[i]. Ainsi, A[i] est un tableau de
taille M dont les composantes sont :

A[i][0], A[i][1] , … , A[i][M]


◼ Par conséquent, le nom A[i] du tableau est équivalent à &A[i][0]
(adresse de A[i][0]).
◼ Par suite, pour tout 0 ≤ j ≤ M-1, nous avons :
A[i]+j est équivalent à &A[i][j]
◼ La matrice A est considérée ici comme un pointeur de pointeur. La
matrice est normalement représentée par un pointeur de pointeur.
73
◼ Exemple : Saisie et affichage d’une matrice.
#define N 10
#define M 20
main( ){
int i, j, A[N][M], *pt;
pt=&A[0][0];
// Saisie
for(i=0;i<N;i++)
for(j=0;j<M;j++){
printf("Entrez A[%d][%d]\n ",i,j);
scanf("%d", pt+i*M+j); }
// Affichage
for(i=0;i<N;i++){
for(j=0;j<M;j++)
printf(" %d \t", *(pt+i*M+j));
printf("\n");}
74
}
POINTEURS DE TYPE POINTEURS
◼ Remarques :
▪ Les différences entre les tableaux et les pointeurs peuvent être
résumées ainsi :
▪ La déclaration d’un pointeur P réserve dans la mémoire uniquement
l’espace mémoire nécessaire pour stocker sa valeur (qui est une autre
adresse).
▪ La déclaration d’un tableau T réserve dans la mémoire un espace
mémoire proportionnel à la taille du tableau et dont l’emplacement est
statique.
▪ La taille du pointeur P peut changer au cours du programme alors que
celle du tableau T reste constante et ne peut pas être modifiée.
▪ Les expressions P=T et P++ sont autorisées mais les instructions T=P
ou T++ ne le sont pas.

75
ALLOCATION DYNAMIQUE DE LA
MÉMOIRE

76
LA FONCTION MALLOC
▪ La fonction malloc sert à demander au système

d’exploitation d’allouer une zone de mémoire d’une


certaine taille dans la heap. Pour l’utiliser, il faut
inclure la librairie stdlib.h.

▪ Voici le prototype de la fonction:

▪ Void *malloc(size_t size);

▪ Elle renvoie un pointeur de type void (qui peut donc

être interprété comme n’importe quel autre type de


donnée) et prend en paramètre une taille en octets. 77
LA FONCTION MALLOC
▪ a) Premier exemple :
Considérez ces instructions :
#include <stdlib.h>
.....
char * adr ;
.....
adr = malloc(50) ;
.....
for (i=0 ; i<50 ; i++) *(adr+i) = 'x' ;

78
LA FONCTION MALLOC
▪ L’appel : malloc (50) alloue un emplacement de
50 octets dans le tas et en fournit l’adresse en
retour. Ici, cette dernière est placée dans le
pointeur adr.

▪ L’instruction for qui vient à la suite n’est donnée


qu’à titre d’exemple d’utilisation de la zone
ainsi créée.

79
LA FONCTION MALLOC

▪ b) Second exemple
long * adr ;
.....
adr = malloc(100 * sizeof(long)) ;
.....
for (i=0 ; i<100 ; i++) *(adr+i) = 1 ;

80
LA FONCTION MALLOC

▪ Cette fois, on a alloué une zone de 100 *


sizeof(long) octets.
▪ Nous l’avons considéré comme une suite
de 100 entiers de type long.
▪ Pour ce faire, vous voyez que nous avons
pris soin de placer le résultat de malloc
dans un pointeur sur des éléments de type
long.

81
LA FONCTION MALLOC

▪ N’oubliez pas que les règles de l’arithmétique des

pointeurs font que : adr + i correspond à l’adresse


contenue dans adr, augmentée de sizeof(long) fois
la valeur de i (puisque adr pointe sur des objets de
longueur sizeof(long)).

82
EXEMPLE DÉTAILLÉ

83
#include <stdio.h>
▪ #include <stdlib.h>

▪ main() {

▪ int i = 3; printf("Adresse de i = %d\n", &i);


▪ int *p; printf("Valeur de p avant initialisation = %d\n",p);
▪ p = (int*)malloc(sizeof(int));

▪ printf("Valeur de p apres initialisation = %d \n",p);

▪ *p=i; // c’est pas une initialisation du pointeur

▪ printf("Valeur de *p apres (*p=i) est %d et de i = %d \n", *p, i);

▪ *p=*p+2;

▪ printf("Valeur de *p apres (*p=*p+2) est %d et de i = %d \n", *p, i);

▪ i=i-1;

▪ printf("Valeur de *p apres (i=i-1) est %d et de i = %d \n", *p, i);

▪ p = &i;

▪ printf("Valeur de *p apres (p=&i) est %d de i est %d \n", *p, i);

▪ printf("valeur de p apres (p=&i) est %d\n", p);} 84


RÉSULTAT AFFICHÉ
◼ Ce programme affichera :
Adresse de i = 2293556
Valeur de p avant initialisation = 41 !!! Adresse quelconque
Valeur de p apres initialisation = 8195928
Valeur de *p apres (*p=i) est 3 et de i = 3
Valeur de *p apres (*p=*p+2) est 5 et de i = 3 // (*p,i) deux vals différentes
Valeur de *p apres (i=i-1) est 5 et de i = 2
Valeur de *p apres (p=&i ) est 2 et de i est 2 // (*p,i) mêmes vals car p
pointe sur i
valeur de p apres (p=&i) = 2293556 // adresse de i

◼ Remarque : Avant la redirection de p vers i, *p n’est pas i .


85
LA FONCTION MALLOC
◼ Dans certains cas, l’opération d’allocation de mémoire échoue (par exemple
dans le cas où il n’y pas assez de mémoire libre ou si l’utilisateur demande de
réserver une grande partie de la mémoire vive).
◼ Il est donc recommandé de tester si l'allocation a marché. Cela consiste à
faire le test suivant :
int *p;
p = (int*)malloc(4*sizeof(int)) ;
if (p == NULL) // Si l'allocation a échoué
exit(0); // On arrête immédiatement le programme
// dans le cas contraire, on continue le
programme (normalement)
◼ Si le pointeur est différent de NULL, le programme peut continuer, sinon il faut
afficher un message d'erreur ou même mettre fin au programme (en utilisant
l’instruction exit(0)), par ce qu'il ne pourra pas continuer correctement s'il n'y a
plus de place en mémoire.

86
◼ Exemple:
#include <stdio.h>
#include <stdlib.h> /* à ajouter */
main() {
int *p,*q, i;
p=malloc(5*sizeof(int)); // on réserve 5 places mémoires pour p
q=malloc(5*sizeof(int)); // on réserve 5 places mémoires pour q
if(p==NULL)
exit(0);
else {
for(i=0;i<5;i++) {
*(p+i)=i+1; /*ou p[i]=i+1; */ initialisation des places
mémoires
printf("%d \t", *(p+i)); } } printf("\n") ;
if(q==NULL)
exit(0);
else {
for (i=0;i<5;i++) {
q[i]=i-1; /* ou *(q+i)=i-1 ; */ initialisation des places
mémoires
printf("%d \t", q[i]); } } }
◼ Après exécution, le programme affiche :
1 2 3 4 5
-1 0 1 2 3
87
REMARQUE SUR LE CAST DE MALLOC
▪ il n'est pas nécessaire de caster le résultat de malloc. Avant le

standard C99, il était courant de caster le résultat de malloc, mais


ce n'est plus nécessaire ni recommandé dans les versions plus
récentes du langage.

▪ La raison principale est que malloc retourne un pointeur générique

(void *). Un pointeur générique peut être implicitement converti en


tout autre type de pointeur en C sans nécessiter de cast explicite.
Cela signifie que lorsque vous assignez le résultat de malloc à un
pointeur, il est automatiquement converti au bon type.

88
LA FONCTION FREE
▪ L’un des intérêts essentiels de la gestion dynamique
est de pouvoir récupérer des emplacements dont on
n’a plus besoin.
▪ Le rôle de la fonction free est de libérer un
emplacement préalablement alloué.
▪ Voici un exemple qui vous montre comment malloc
peut profiter d’un espace préalablement libéré sur le
tas.

89
LA FONCTION FREE
EXEMPLE D’UTILISATION DE LA FONCTION FREE
#include <stdio.h>
#include <stdlib.h>
main() { char * adr1, * adr2, * adr3 ;
adr1 = malloc (100) ;
printf ("allocation de 100 octets en %p\n", adr1) ;
adr2 = malloc (50) ;
printf ("allocation de 50 octets en %p\n", adr2) ;
free (adr1) ;
printf ("libération de 100 octets en %p\n", adr1) ;
adr3 = malloc (40) ;
printf ("allocation de 40 octets en %p\n", adr3) ; }

90
RÉSULTAT DU PROGRAMME

91
RÉSULTAT DU PROGRAMME

▪ Notez que la dernière allocation a pu se faire dans

l’espace libéré par le précédent appel de free.

92
AUTRES OUTILS DE GESTION
DYNAMIQUE : CALLOC ET REALLOC

▪Bien qu’elles soient moins


fondamentales que les
précédentes, les deux fonctions
calloc et realloc peuvent
s’avérer pratiques dans
certaines circonstances.
93
LA FONCTION CALLOC
▪ La fonction :
void * calloc ( size_t nb_blocs , size_t taille )
(stdlib.h)

▪ alloue l’emplacement nécessaire à nb_blocs


consécutifs, ayant chacun une taille de taille
octets.

94
LA FONCTION CALLOC
▪ Contrairement à ce qui se passait avec malloc, où le

contenu de l’espace mémoire alloué était imprévisible,

▪ calloc remet à zéro chacun des octets de la zone ainsi

allouée.

95
LA FONCTION REALLOC

▪ La fonction :

▪ void * realloc (void * pointeur , size_t taille )

(stdlib.h)

▪ permet de modifier la taille d’une zone

préalablement allouée (par malloc, calloc ou


realloc).

96
LA FONCTION REALLOC

▪ Le pointeur doit être l’adresse de début de la zone


dont on veut modifier la taille.
▪ Quant à taille, elle représente la nouvelle taille
souhaitée.
▪ Cette fonction restitue l’adresse de la nouvelle zone
ou un pointeur nul dans le cas où l’allocation a
échoué.

97
LA FONCTION REALLOC

▪ Lorsque la nouvelle taille demandée est


supérieure à l’ancienne, le contenu de
l’ancienne zone est conservé (quitte à le
recopier si la nouvelle adresse est différente de
l’ancienne).
▪ Dans le cas où la nouvelle taille est inférieure à
l’ancienne, le début de l’ancienne zone (c’est-à-
dire taille octets) verra son contenu inchangé

98

Vous aimerez peut-être aussi