Atelier de Programmation Procédurale 1
(Langage C)
Niveaux: Info 1
Dr Nafaa Haffar
Année universitaire:
2023/2024
Les pointeurs
Les pointeurs 1
Présentation de la problématique
Problématique
Imaginez par exemple que vous souhaitez résider dans une ville pendant une période donnée!
Chercher un logement (une maison)
Comment trouver un logement ?
Que Faire ?????
Contacter une agence immobilière (location de maisons)?
L’agence va vous affecter à un logement qui est
adapté à vos besoins.
Les pointeurs 2
Présentation de la problématique
Problématique
Si nous projetons cette problématique sur le mécanisme de gestion de la mémoire?
La Maison ⇔ L’espace mémoire où la variable sera stockée
(Adresse)
L’agence ⇔ Le système d’exploitation qui va gérer la mémoire
La ville ⇔ La mémoire (RAM)
La personne ⇔ La variable qu’on veut stocker
Les pointeurs 3
1) Introduction
Une variable correspond à un emplacement en mémoire (adresse) où se trouve une valeur.
Accéder à sa valeur en lecture et en écriture
int n ;
n = 5 ; // écriture de la valeur de n (initialisation de n)
printf("n vaut %d", n) ; // lecture de la valeur de n
Accéder à son adresse en lecture seulement, car l'adresse (l'emplacement mémoire) est choisie par le système.
printf("L'adresse de n est %d", &n) ; // lecture de l'adresse de n
mémoire
Adresse 1 (0XAABA) Résultat (Affichage)
Adresse 2 (0XAABC) 5
… n vaut 5
L'adresse de n est 0XAABC
Adresse n (0XAABN)
Les pointeurs 4
1) Introduction
► Pointeur ?
Un pointeur est une donnée qui représente l'adresse d'une variable.
Pour que l'adresse stockée dans un pointeur soit exploitable :
il faut connaître le type de l'information qui se situe au niveau de cette adresse.
Pour cette raison, un pointeur doit être toujours associé à un type donné.
Comment gérer les pointeurs ?
Les pointeurs 5
2) Déclaration
Formellement la syntaxe de déclaration d'un pointeur est la suivante :
Syntaxe :
Type* identificateur ; // La variable identificateur est un pointeur vers une valeur de type Type.
Exemple :
char* c ; // déclaration d’un pointeur nommé (c) qui pointe vers une donnée de type caractère.
int* i ; // déclaration d’un pointeur nommé (i) qui pointe vers une donnée de type entier.
float* d ; // déclaration d’un pointeur nommé (d) qui pointe vers une donnée de type réel double.
pointeurs mémoire
c
A
Rq : Les pointeurs occupent tous la même taille
i 12
en mémoire indépendamment de la taille du 14.65
type de l'objet pointé d
Les pointeurs 6
2) Déclaration
Il est possible de faire une déclaration multiple de pointeurs à travers la syntaxe suivante :
Syntaxe :
Type* identificateur1 , … , * identificateur n ; // Les variables identificateurs1 .. n sont des pointeurs vers des valeurs de type
Type.
Exemple :
int * p1 , *p2 ; // déclaration des pointeurs nommés (p1, p2) qui pointent vers des données de type entier.
int *p1 , p2 ; // déclaration d’un pointeur nommé (p1) qui pointe vers une donnée de type entier et une variable (p2) de type entier.
int p1 , *p2 ; // déclaration d’une variable (p1) de type entier et un pointeur nommé (p2) qui pointe vers une donnée de type entier.
Les pointeurs 7
3) Initialisation
La valeur initiale d’un pointeur est l'adresse d'une donnée dans la mémoire.
L'initialisation d'un pointeur ne peut s'effectuer que si la variable est déjà existante dans la mémoire.
L'initialisation d'un pointeur se fait à travers la syntaxe suivante :
Syntaxe :
Type* identificateur = &variable ; // initialisation d’un pointeur (identificateur) par l’adresse de la variable de type type.
Exemple :
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi = &i ; // initialisation d’un pointeur nommé (pi) avec l’adresse de la variable (i).
Rq : Le pointeur ne doit pointer que vers une variable de même type, sinon le compilateur affiche alors une erreur lors de la compilation
char c ; // déclaration d’une variable nommée (c) de type caractère
int * p = &c ; // Erreur! Puisque p n'est pas un pointeur vers un caractère (p est un pointeur vers un entier )
Les pointeurs 8
4) Affectation des pointeurs et conversion
► Affectation
Une variable de type pointeur peut obtenir sa valeur non seulement par une initialisation, mais également par une opération d'affectation.
Syntaxe
Type* identificateur1 , *identificateur2 ; // Les variables identificateurs1 .. n sont des pointeurs vers des valeurs de type type.
identificateur1 = identificateur2 ; // Affectation de la valeur de l’identificateurs2 vers identificateurs1
Exemple
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi1, *pi2 ; // déclaration des pointeurs nommés (pi1, pi2) qui pointent vers des données de type entier.
pi1 =&i; // pi1 contient l'adresse i
pi2 = pi1; // pi2 contient l'adresse i
Rq : Les pointeurs doivent être de même type
pi1 0XAABC
= i
pi2 0XAABC
mémoire
Les pointeurs 9
4) Affectation des pointeurs et conversion
► Conversion
Contrairement aux autres types de variables, il n'existe aucune conversion implicite d'un type pointeur vers un autre type pointeur.
Le seul moyen pour faire des conversions entre types pointeurs est le casting (Conversion forcée).
Syntaxe :
Type1* identificateur1 ; Type2* identificateur2 ; // identificateur1 et identificateur2 sont des pointeurs sur Type1 et Type2 .
identificateur1 = (Type1*) identificateur2 ; // Conversion forcée de type de l’identificateur2 (Type2) vers Type1
Exemple :
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi = &i ; // initialisation d’un pointeur nommé (pi) de type (int) par l’adresse de la variable (i).
unsigned int *piu ; // déclaration d’un pointeur nommé (piu) qui pointe vers une donnée de type entier non signé.
pui = pi; // Erreur Type incompatible (affectation d’un entier à un entier non signé)
pui=(unsigned int *) pi; // Valide
// Correction de l’incompatibilité en fonçant la conversion de type de pointeur (pi) en un entier non
signé
Les pointeurs 10
5) Accès indirect aux variables
Il est possible d'accéder à une variable à travers un pointeur qui pointe vers cette variable.
Il suffit d'utiliser pour cela l'opérateur * de la manière suivante :
Syntaxe :
Type NomVariable, *NomPointeur ; // Déclaration d’une variable et un pointeur de type Type.
NomPointeur = &NomVariable ; // Affectation de l’adresse de la variable au pointeur
Alors on peut accéder à une variable de deux manières : NomVariable ⇔ * NomPointeur
Exemple :
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi ; // déclaration d’un pointeur nommé (pi) de type entier
i = 5; // initialisation de la variable i à 5.
pi = &i; // pi contient l'adresse de i L’utilisation de (*pi) désigne d'une manière indirecte le contenu de i
printf("i vaut %d", i) ; // i vaut 5
printf("i vaut %d", *pi) ; // *pi vaut 5
Les pointeurs 11
5) Accès indirect aux variables
L'opérateur * permet de manipuler en lecture et en écriture la valeur d'une variable à travers un pointeur.
Exemple :
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi ; // déclaration d’un pointeur nommé (pi) de type entier
Habituellement, on passerait l'adresse de la
pi = &i ; // pi contient l'adresse de i
variable en utilisant &i, mais dans ce cas, pi
scanf("%d",pi) ; // Saisie (valeur saisie est 3)
représente déjà l'adresse de i.
printf("i vaut %d", *pi) ; // Affichage (valeur affichée est 3)
mémoire pointeur
Résultat (Affichage)
Adresse 1 (0XAABA)
0XAABC pi
Adresse 2 (0XAABC) 3 printf("%d", *pi) ; // 3
… printf("%p", pi) // 0XAABC
Adresse n (0XAABN)
Les pointeurs 12
6) Incrémentation de pointeurs et addition
► Incrémentation
L'incrémentation d'un pointeur donne l'adresse située à sizeof (TYPE) octets à partir de la valeur courante du pointeur.
TYPE étant le type de la variable pointée par le pointeur.
Exemple
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi ; // déclaration d’un pointeur nommé (pi) de type entier
pi = &i ; // pi contient l'adresse de i Supposons que pi vaut 1200
pi++; // incrémente p de 4 octets ⇔ pi = pi +1
pi vaut 1200 + 1 * sizeof(int) = 1200 + 1*4 = 1204
Les pointeurs 13
6) Incrémentation de pointeurs et addition
► Addition
L'addition entre un pointeur et un entier (n) donne l'adresse située à n*sizeof (TYPE) à partir de la valeur
courante du pointeur.
Exemple
Supposons que i vaut 1200
int* i; int* j ; // déclaration des pointeurs nommés (i, j) qui pointent vers des données de type entier.
j vaut 1200 + 10 * sizeof(int)
int k ; // déclaration d’une variable nommée (k) de type entier
= 1200 + 10*4 = 1240
i = &k ; // i contient l'adresse de k
⇔ j = j +1
j = i+ 10 ; // j contient la valeur de i +10 j vaut 1240 + 1 * sizeof(int)
= 1240 + 1*4 = 1244
j++ ; // incrémentation de j
Rq : l'addition n'est définie qu'entre un pointeur et un entier ( addition entre deux pointeurs | entre un pointeur et un nombre en virgule )
Les pointeurs 14
7) Décrémentation de pointeurs et soustraction
► Décrémentation
La décrémentation d'un pointeur donne l'adresse située à - sizeof (TYPE) octets à partir de la valeur courante du pointeur.
TYPE étant le type de la variable pointée par le pointeur.
Exemple :
int i ; // déclaration d’une variable nommée (i) de type entier
int * pi ; // déclaration d’un pointeur nommé (pi) de type entier Supposons que pi vaut 1200
pi = &i ; // pi contient l'adresse de i ⇔ pi = pi - 1
pi vaut 1200 - 1 * sizeof(int)
pi--; // décrémente pi de 4 octets
= 1200 - 1*4 = 1196
Les pointeurs 15
7) Décrémentation de pointeurs et soustraction
► Soustraction
La soustraction d’un entier (n) à partir d’un pointeur donne l'adresse située à -n*sizeof (TYPE).
Contrairement à l'addition, on peut soustraire d'un pointeur non seulement un entier, mais aussi un autre pointeur de même type.
Exemple :
int* i; int* j ; // déclaration des pointeurs nommés (i, j) qui pointent vers des données de type entier.
int Tab [5] ; // déclaration d’un tableau nommé (Tab) de type entier
i = &Tab [1] ; // i contient l'adresse de l’élément 1 de tableau (Tab)
j = &Tab [4] ; // j contient l'adresse de l’élément 4 de tableau (Tab)
printf("i vaut %d", j-i) ; //donne 3 ⇔ la soustraction entre deux pointeurs donne le nombre des éléments
i 1200 j 1212
0 1 2 3 4
Tab
4 octets
Les pointeurs 16
8) Comparaison entre pointeurs
La comparaison de pointeurs est possible, mais seulement entre pointeurs de même type.
Exemple 1
int* p1; int* p2 ; // déclaration des pointeurs nommés (p1, p2) qui pointent vers des données de type entier.
if (p1 == p2) // Tester si les pointeurs (p1, p2) pointent vers la même donnée ou non
printf("Les deux pointeurs pointent vers la même donnée") ;
else
printf("Les deux pointeurs pointent vers des données différentes") ;
Exemple 2
int* p1; int* p2 ; // déclaration des pointeurs nommés (p1, p2) qui pointent vers des données de type entier.
if (p2 > p1) // Tester si le pointeur (p2) contient une adresse plus grande à celle de pointeur (p1)
printf("Le pointeur p2 contient une adresse plus grande que le pointeur p1") ;
Les pointeurs 17
9) Pointeurs génériques
► Définition
Il existe en C des pointeurs particuliers appelés « pointeurs génériques ».
Ces types de pointeurs peuvent pointer vers des données de n’importe quel type (entier, réel, …).
Les pointeurs génériques s'avèrent utiles si :
l'adresse d'une zone mémoire doit être enregistrée, mais que le type de donnée n'est pas encore établi.
on ne pas se fixer dans l'immédiat sur un quelconque type de données.
Les pointeurs 18
9) Pointeurs génériques
► Déclaration
Les pointeurs génériques sont déclarés à l'aide du type : void*
La déclaration se fait comme suit :
Syntaxe :
void* identificateur ; // Déclaration d’un pointeur générique.
Exemple :
int i ; double d; // Déclaration de deux variables i de type entier et d de type double
void * vp; // pointeur générique
vp=&i; // vp contient l'adresse de la variable i
vp=&d; // vp contient l'adresse de la variable d
Les pointeurs 19
9) Pointeurs génériques
► Restrictions
Un pointeur générique ne peut pas servir pour faire des accès indirects aux contenus des variables.
Exemple :
int i ; // Déclaration d’une variable i de type entier
void * vp; // pointeur générique
Le compilateur ne peut pas déterminer le pas de déplacement (octets?)
vp=&i; // vp contient l'adresse de la variable i
ce qui peut entraîner un calcul incorrect ou provoquer une erreur
vp++; // incrémentation de vp
vp 1200 vp 1201
char
(char*) vp++
Solution
vp 1204 (int*) vp++
int
Conversion explicite (casting) de
vp 1208 vp comme suit (double*) vp++
double
Les pointeurs 20
9) Pointeurs génériques
► Affectation d'un pointeur générique à un pointeur d'un autre type
Avec certains compilateurs, le code peut compiler et
Également, l'affectation du contenu d'un pointeur générique (void*) à un pointeur (type*) doit obligatoirement passer
s'exécuter sans erreur même s'il viole les règles du
par le casting.
langage C.
Exemple :
Cependant, cela ne signifie pas que le code est sûr ou
int i ; // déclaration d’une variable de type entier correct
int* pi1; int* pi2 ; // déclaration des pointeurs nommés (pi1, pi2) qui pointent vers des données
peut conduire de type entier.
à des résultats imprévisibles ou à des
pi1 = &i ; // pi1 contient l'adresse de i erreurs difficilement détectable
void * pg; // pg est un pointeur générique
pg = pi1; // valide! pg reçoit la valeur du pointeur pi
pi2 = pg ; // erreur ! Affectation n’est pas possible d’un pointeur générique à un pointeur de type int (casting)
pi2 = (int*) pg ; // valide ! Affectation possible d’un pointeur générique à un pointeur de type int avec le casting
printf("%d", *pi2); // valide ! opération possible, car le type de pi2 est connu int*
pi2++; // valide ! opération possible, car le type de pi2 est connu int*
Rq : La conversion implicite entre un pointeur générique et un pointeur type se fait comme suit: Type* vers void* v | void* vers Type* × casting
Les pointeurs 21
10) Pointeurs et tableaux
► Définition
En C, le nom d'un tableau est considéré comme une constante d'adresse définissant l'emplacement de début à partir
duquel vont se succéder les éléments du tableau.
Lorsqu'il est employé seul, l'identificateur d'un tableau est considéré comme un pointeur constant.
Étant donnée la déclaration suivante :
int T[5] ;
T 0 1 2 3 4
Dans ce cas T est considéré comme un pointeur constant.
Les pointeurs 22
10) Pointeurs et tableaux
► Notation d'accès aux éléments d'un tableau
Étant donnée la déclaration suivante :
int T[5] ;
Nous avons les notations équivalentes suivantes :
T ⇔ &T[0] || T+1 ⇔ &T[1] || T+i ⇔ &T[i] // équivalence d’adresses
*T ⇔ T[0] || *(T+1) ⇔ T[1] || *(T+i) ⇔ T[i ] // équivalence des accès
Exemples : Remplissage Remplissage avec pointeur
classique
int T[5], i ; int T[5], i ;
for(i=0 ; i<5 ; i++){ for(i=0 ; i<5 ; i++){
T[i] = i; *(T+i) = i ;
} }
Les pointeurs 23
10) Pointeurs et tableaux
► Notation d'accès aux éléments d'un tableau
Le nom du tableau ne peut pas être utilisé pour faire le parcours des éléments.
Étant donnée la déclaration suivante :
int T[10] ;
L'incrémentation T++ est incorrecte.
T est dans ce cas considéré comme un pointeur constant
Si T est utilisé, on risque de perdre l’index du tableau
Solution ? l'utilisation d'une variable d’un autre pointeur pour faire ce parcours
Exemple
p 1200 … p 1216
int T[5], i ,*p ;
for(i=0, p=T ; i<5 ; i++, p++) 0 1 2 3 4
*p= i ; T 0 1 2 3 4
Les pointeurs 24
11) Pointeurs et constantes
► Pointeur en lecture seule
Un pointeur en lecture seule peut pointer sur n'importe quelle variable.
Il ne permet toutefois que l'accès en lecture à la variable pointée.
Toute tentative de modification (accès en écriture) est signalée comme erreur.
Les pointeurs 25
11) Pointeurs et constantes
► Pointeur en lecture seule
La déclaration d’un pointeur en lecture seule se fait à travers la syntaxe suivante :
Syntaxe
const Type* NomPointeur ;
Exemple
int i=5, j=10; // déclaration de deux variables (i, j) de type entier
const int* p = &i; // valide ! p pointeur contient l’adresse de i
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 5
p = &j; // valide ! p pointeur contient l’adresse de j
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 10
*p = 8; // erreur! (tentative de modification) p est un pointeur en lecture seule
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 10
error: assignment of read-only location ‘*p’
Les pointeurs 26
11) Pointeurs et constantes
► Pointeur en lecture seule
Indication :
On ne peut pas affecter l'adresse d'un objet constant à un pointeur sur un type non constant
une telle affectation pourrait permettre de modifier cet objet par l'intermédiaire du pointeur.
Exemple :
const int i=5 ; // déclaration d’une constante (i) de type entier
const int* p = &i ; // valide ! p pointeur qui contient l’adresse de i
int* q = &i ; // avertissement ! affectation de l'adresse de la constante (i) à un pointeur sur un type non constant (q)
warning: initialization discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
p mémoire
const
0XAABC
q
0XAABC 5 i const
0XAABC ….
Les pointeurs 27
11) Pointeurs et constantes
► Pointeur constant
Un pointeur constant est un pointeur qui ne peut pointer que sur une seule variable.
Le contenu de la variable pointée n'est pas nécessairement constant et peut par conséquent être modifié.
La déclaration d’un tel type de pointeur se fait de la manière suivante :
Syntaxe :
Type* const NomPointeur ;
Exemple :
int i=5, j=10; // déclaration de deux variables (i,j) de type entier
int* const p = &i ; // valide ! p pointeur contient l’adresse de i
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 5
i=8; // Modification de la valeur de la variable i
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 8
p = &j ; // erreur ! Le pointeur p ne peut pointer que sur une seule variable qui est (i)
printf("Valeur pointée : %d ", *p); // Affichage de la valeur pointé par p qui est 8
Les pointeurs 27
Exercice d’application A B C P1 P2
Initialisation 1 2 3 / /
► Pointeur constant
p1=&A; 1 2 3 &A /
Soit le programme suivant:
p2=&C; 1 2 3 &A &C
*p1=(*p2)++; 3 2 4 &A &C
int main() { p2=&B;
int A=1; *p1-=*p2; p1=p2; 3 2 4 &C &C
int B=2; ++*p2; p2=&B; 3 2 4 &C &B
int C=3; *p1*=*p2; *p1-=*p2; 3 2 2 (4-2) &C &B
int *p1,*p2; A=++*p2**p1;
++*p2; 3 3 (2+1) 2 &C &B
p1=&A; p1=&A;
*p1*=*p2; 2 3 6 (2*3) &C &B
p2=&C; *p2=*p1/=*p2;
A=++*p2**p1; 24 (4*6) 4 (3+1) 6 &C &B
*p1=(*p2)++; return 0;
p1=p2; } p1=&A; 24 4 6 &A &B
*p2=*p1/=*p2; 6 (24/4) 6 6 &A &B
Rq : Res = ++x Incrémentation + Affectation
Res = x++ Affectation +Incrémentation