0% ont trouvé ce document utile (0 vote)
28 vues11 pages

Annexe1 Pointeur

Transféré par

Angélica DEKE
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 DOCX, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
28 vues11 pages

Annexe1 Pointeur

Transféré par

Angélica DEKE
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 DOCX, PDF, TXT ou lisez en ligne sur Scribd

Chapitre 9 : LES POINTEURS :

L'importance des pointeurs en C


La plupart des langages de programmation offrent la possibilité d'accéder aux données dans la
mémoire de l'ordinateur à l'aide de pointeurs, c’est-à-dire à l'aide de variables auxquelles on
peut attribuer les adresses d'autres variables.
En C, les pointeurs jouent un rôle primordial dans la définition de fonctions : comme le
passage des paramètres en C se fait toujours par la valeur, les pointeurs sont le seul moyen de
changer le contenu de variables déclarées dans d'autres fonctions. Ainsi le traitement de
tableaux et de chaînes de caractères dans des fonctions serait impossible sans l'utilisation de
pointeurs (voir Chapitre 10).
En outre, les pointeurs nous permettent d'écrire des programmes plus compacts et plus
efficients et fournissent souvent la seule solution raisonnable à un problème. Ainsi, la
majorité des applications écrites en C profitent excessivement des pointeurs.
Le revers de la médaille est très bien formulé par Kernighan & Ritchie dans leur livre
'Programming in C'
:
" ... Les pointeurs étaient mis dans le même sac que l'instruction goto comme une excellente
technique de formuler des programmes incompréhensibles. Ceci est certainement vrai si les
pointeurs sont employés négligemment, et on peut facilement créer des pointeurs qui pointent
'n'importe où'. Avec une certaine discipline, les pointeurs peuvent aussi être utilisés pour
programmer de façon claire et simple. C'est précisément cet aspect que nous voulons faire
ressortir dans la suite. ..."
Cette constatation a ensuite motivé les créateurs du standard ANSI-C à prescrire des règles
explicites pour la manipulation des pointeurs.
I) Adressage de variables :
Avant de parler de pointeurs, il est indiqué de passer brièvement en revue les deux
modes d'adressage principaux, qui vont d'ailleurs nous accompagner tout au long des
chapitres suivants.

1) Adressage direct :
Dans la programmation, nous utilisons des variables pour stocker des informations.
La valeur d'une variable se trouve à un endroit spécifique dans la mémoire interne de
l'ordinateur. Le nom de la variable nous permet alors d'accéder directement à cette
valeur.
Adressage direct : Accès au contenu d'une variable par le nom de la
variable. Exemple :

2) Adressage indirect :
Si nous ne voulons ou ne pouvons pas utiliser le nom d'une variable A, nous pouvons
copier l'adresse de cette variable dans une variable spéciale P, appelée pointeur.
Ensuite, nous pouvons retrouver l'information de la variable A en passant par le pointeur
P.
Adressage indirect : Accès au contenu d'une variable, en passant par un pointeur qui
contient l'adresse de la variable.
Exemple :
Soit A une variable contenant la valeur 10 et P un pointeur qui contient l'adresse
de A. En mémoire, A et P peuvent se présenter comme suit :

II) Les pointeurs :


Définition : Pointeur
Un pointeur est une variable spéciale qui peut contenir l'adresse d'une autre variable.
En C, chaque pointeur est limité à un type de données. Il peut contenir l'adresse d'une
variable simple de ce type ou l'adresse d'une composante d'un tableau de ce type. Si un
pointeur P contient l'adresse d'une variable A, on dit que 'P pointe sur A'.
Remarque :
Les pointeurs et les noms de variables ont le même rôle : Ils donnent accès à un
emplacement dans la mémoire interne de l'ordinateur. Il faut quand même bien faire la
différence :
* Un pointeur est une variable qui peut 'pointer' sur différentes
adresses. * Le nom d'une variable reste toujours lié à la même
adresse.

1) Les opérateurs de base :


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.

- d'une syntaxe de déclaration pour pouvoir déclarer un


pointeur.
a) L'opérateur 'adresse de' : &

&<NomVariable>

fournit l'adresse de la variable <NomVariable>

L'opérateur & nous est déjà familier par la fonction scanf, qui a besoin de
l'adresse de ses arguments pour pouvoir leur attribuer de nouvelles valeurs.
Exemple :

int N;
printf("Entrez un nombre entier : ");
scanf("%d", &N);
Attention !
L'opérateur & peut seulement être appliqué à des objets qui se trouvent dans la
mémoire interne, c’est-à-dire à des variables et des tableaux. Il ne peut pas être
appliqué à des constantes ou des expressions.
Représentation
schématique : Soit
P un pointeur non
initialisé

et A une variable (du même type) contenant la valeur 10 :

Alors l'instruction
P = &A;
affecte l'adresse de la variable A à la variable P. Nous pouvons illustrer le fait que
'P pointe sur A' par une flèche :

b) L'opérateur 'contenu de' : *

*<NomPointeur> désigne le contenu de l'adresse


référencée par le pointeur <NomPointeur>
Exemple :
Soit A une variable contenant la valeur 10, B une variable contenant la valeur 50
et P un pointeur non initialisé :

Après les instructions,


P = &A;
B = *P;
*P = 20;

- P pointe sur A,
- le contenu de A (référencé par *P) est
affecté à B, et - le contenu de A
(référencé par *P) est mis à 20.

c) Déclaration d'un pointeur :


<Type> *<NomPointeur> déclare un
pointeur <NomPointeur> qui peut
recevoir des adresses de variables
du type <Type>
Une déclaration comme
int *PNUM; peut
être interprétée comme
suit : "*PNUM est du
type int"
ou
"PNUM est un pointeur
sur int" ou
"PNUM peut contenir l'adresse d'une variable du type int"
Exemple :
Le programme complet effectuant les transformations de l'exemple ci-dessus peut
se présenter comme suit :
|main() ou bien |main()
|{ |{
| /* déclarations */ |/* déclarations */
| short A = 10; | short A, B, *P;
| short B = 50; | /* traitement */
| short *P; | A = 10;
| /* traitement */ | B = 50;
| P = &A; | P = &A;
| B = *P; | B = *P;
| *P = 20; | *P = 20;
| return (0); | return (0);
|} |}
Remarque :
Lors de la déclaration d'un pointeur en C, ce pointeur est lié explicitement à un
type de données. Ainsi, la variable PNUM déclarée comme pointeur sur int ne peut
pas recevoir l'adresse d'une variable d'un autre type que int.
Nous allons voir que la limitation d'un pointeur à un type de variables n'élimine
pas seulement un grand nombre de sources d'erreurs très désagréables, mais permet
une série d'opérations très pratiques sur les pointeurs

2) Les opérations élémentaires sur pointeurs :


En travaillant avec des pointeurs, nous devons observer les règles

suivantes : a) Priorité de :

• * et &
• Les opérateurs * et & ont la même priorité que les autres opérateurs unaires
(la négation !, l'incrémentation ++, la décrémentation --). Dans une même
expression, les opérateurs unaires *, &, !, ++, -- sont évalués de droite à
gauche.
• Si un pointeur P pointe sur une variable X, alors *P peut être utilisé partout où
on peut écrire X.
Exemple :
Après l'instruction
P = &X; les expressions
suivantes, sont équivalentes :
Y = *P+1 Y = X+1
*P = *P+10 X = X+10
*P += 2 X += 2
++*P ++X
(*P)++ X++
Dans le dernier cas, les parenthèses sont nécessaires :
Comme les opérateurs unaires * et ++ sont évalués de droite à gauche, sans les
parenthèses le pointeur P serait incrémenté, non pas l'objet sur lequel P pointe.
On peut uniquement affecter des adresses à un pointeur.
b) Le pointeur NUL :
Seule exception : La valeur numérique 0 (zéro) est utilisée pour indiquer qu'un
pointeur ne pointe 'nulle part'.

int *P;
P = 0;
Finalement, les pointeurs sont aussi des variables et peuvent être utilisés comme
telles. Soit P1 et P2 deux pointeurs sur int, alors l'affectation
P1 = P2;
copie le contenu de P2 vers P1. P1 pointe alors sur le même

objet que P2. c) Résumons :

Après les instructions :


in
t
A;
in
t
*P
;
P = &A;
A désigne le contenu de A
&A désigne l'adresse de A

P désigne l'adresse de A

*P désigne le
contenu de A En outre :
&P désigne l'adresse du pointeur P
*A est illégal (puisque A n'est pas un pointeur)

• Exercice 9.1

III) Pointeurs et tableaux :


En C, il existe une relation très étroite entre tableaux et pointeurs. Ainsi, chaque
opération avec des indices de tableaux peut aussi être exprimée à l'aide de pointeurs. En
général, les versions formulées avec des pointeurs sont plus compactes et plus efficientes,
surtout à l'intérieur de fonctions. Mais, du moins pour des débutants, le 'formalisme
pointeur' est un peu inhabituel.

1) Adressage des composantes d'un tableau :


Comme nous l'avons déjà constaté au chapitre 7, le nom d'un tableau représente
l'adresse de son premier élément. En d'autres termes :
&tableau[0] et tableau
sont une seule et même adresse.
En simplifiant, nous pouvons retenir que le nom d'un tableau est un pointeur
constant sur le premier élément du tableau.
Exemple :
En déclarant un tableau A de type int et un pointeur P sur int,
int A[10];
int *P;
l'instruction :
P = A; est équivalente à P = &A[0] ;

Si P pointe sur une composante quelconque d'un tableau, alors P+1 pointe sur la
composante suivante. Plus généralement,
P+i pointe sur la i-ième composante
derrière P et P-i pointe sur la i-ième composante devant
P. Ainsi, après l'instruction,
P = A;
le pointeur P pointe sur A[0], et
*(P+1) désigne le contenu de A[1]
*(P+2) désigne le contenu
de A[2] ... ...
*(P+i) désigne le contenu de A[i]
Remarques :
Au premier coup d’œil, il est bien surprenant que P+i n'adresse pas le i-ième octet
derrière P, mais la i-ième composante derrière P ...
Ceci s'explique par la stratégie de programmation 'défensive' des créateurs du
langage C :
Si on travaille avec des pointeurs, les erreurs les plus perfides sont causées par des
pointeurs mal placés et des adresses mal calculées. En C, le compilateur peut calculer
automatiquement l'adresse de l'élément P+i en ajoutant à P la grandeur d'une
composante multipliée par i. Ceci est possible, parce que :
- chaque pointeur est limité à un seul type de données, et
- le compilateur connaît le nombre d'octets des différents types.
Exemple :
Soit A un tableau contenant des éléments du type float et P un pointeur sur float :
float A[20],
X; float *P;
Après les instructions,
P = A;
X = *(P+9);
X contient la valeur du 10-ième élément de A, (c’est-à-dire celle de A[9]). Une
donnée du type float ayant besoin de 4 octets, le compilateur obtient l'adresse P+9 en
ajoutant 9 * 4 = 36 octets à l'adresse dans P.
Rassemblons les constatations ci dessus :
Comme A représente l'adresse de
A[0],
*(A+1) Désigne le contenu de A[1]
*(A+2) Désigne le contenu de A[2]
...
*(A+i) Désigne le contenu de
A[i] Attention !
Il existe toujours une différence essentielle entre un pointeur et le nom d'un
tableau :

- Un pointeur est une variable, donc des opérations comme P = A ou P++ sont
permises. - Le nom d'un tableau est une constante, donc des opérations comme A
= P ou A++ sont impossibles.

Ceci nous permet de jeter un petit coup d’œil derrière les rideaux :
Lors de la première phase de la compilation, toutes les expressions de la forme
A[i] sont traduites en *(A+i). En multipliant l'indice i par la grandeur d'une
composante, on obtient un indice en octets :
<indice en octets> = <indice élément> * <grandeur élément>
Cet indice est ajouté à l'adresse du premier élément du tableau pour obtenir
l'adresse de la composante i du tableau. Pour le calcul d'une adresse donnée par une
adresse plus un indice en octets, on utilise un mode d'adressage spécial connu sous le
nom 'adressage indexé' :
<adresse indexée> = <adresse> + <indice en octets>
Presque tous les processeurs disposent de plusieurs registres spéciaux (registres
index) à l'aide desquels on peut effectuer l'adressage indexé de façon très efficace.
Résumons :
Soit un tableau A d'un type quelconque et i un indice pour les composantes de A,
alors
A désigne l'adresse de
A[0] A+i désigne
l'adresse de A[i]
*(A+i) désigne le contenu de A[i]
Si P = A, alors
P pointe sur l'élément
A[0] P+i pointe sur
l'élément A[i]
*(P+i) désigne le contenu de A[i]
Formalisme tableau et formalisme pointeur :
A l'aide de ce bagage, il nous est facile de 'traduire' un programme écrit à l'aide du
'formalisme tableau' dans un programme employant le 'formalisme pointeur'.
Exemple :
Les deux programmes suivants copient les éléments positifs d'un tableau T dans
un deuxième tableau POS.
Formalisme tableau :
main() { int T[10] = {-3, 4, 0, -7, 3,
8, 0, -1, 4, -9}; int POS[10]; int
I,J; /* indices courants dans T et POS
*/ for (J=0,I=0 ; I<10 ; I++) if
(T[I]>0) {
POS[J] = T[I];

J++;
}
return
(0);
}
Nous pouvons remplacer systématiquement la notation tableau[I] par *(tableau +
I), ce qui conduit à ce programme :
Formalisme pointeur :
main()
{ int T[10] = {-3, 4, 0, -7, 3, 8, 0, -
1, 4, -9}; int POS[10]; int I,J; /*
indices courants dans T et POS */ for
(J=0,I=0 ; I<10 ; I++) if
(*(T+I)>0) {
*(POS+J) = *(T+I);

J++;
}
return
(0); }
Sources d'erreurs :
Un bon nombre d'erreurs lors de l'utilisation de C provient de la confusion entre
soit contenu et adresse, soit pointeur et variable. Revoyons donc les trois types de
déclarations que nous connaissons jusqu'ici et résumons les possibilités d'accès aux
données qui se présentent.
Les variables et leur utilisation int A ; déclare une variable simple du type int
A désigne le
contenu de A
&A désigne
l'adresse de A
int B[] ; déclare un tableau d'éléments du type int
B désigne l'adresse
de la première
composante de B.
(Cette adresse est toujours constante)
B[i] désigne le contenu de la composante i du tableau
&B[i] désigne l'adresse de la composante i du tableau
en utilisant le formalisme pointeur :
B+i désigne l'adresse de la composante i du tableau
*(B+i) désigne le contenu de la composante i du tableau
int *P ; déclare un pointeur sur des éléments du type int.
P peut pointer sur des variables simples du type
int ou sur les composantes d'un tableau du
type int.

P désigne l'adresse contenue dans P


(Cette adresse est variable)
*P désigne le contenu de l'adresse dans P
Si P pointe dans un tableau, alors
P désigne l'adresse de la première composante
P+i désigne l'adresse de la i-ième composante derrière P
*(P+i) désigne le contenu de la i-ième composante derrière P

• Exercice 9.2

2) Arithmétique des pointeurs :


Comme les pointeurs jouent un rôle si important, le langage C soutient une série
d'opérations arithmétiques sur les pointeurs que l'on ne rencontre en général que dans les
langages machines. Le confort de ces opérations en C est basé sur le principe suivant :
Toutes les opérations avec les pointeurs tiennent compte automatiquement du type et de la
grandeur des objets pointés.

a) - Affectation par un pointeur sur le même type :


Soient P1 et P2 deux pointeurs sur le même type de données, alors l'instruction
P1 = P2;
fait pointer P1 sur le même objet que P2

b) - Addition et soustraction d'un nombre entier :


Si P pointe sur l'élément A[i] d'un tableau, alors
P+n pointe
sur A[i+n] P-n
pointe sur A[i-n]
c) - Incrémentation et décrémentation d'un pointeur :
Si P pointe sur l'élément A[i] d'un tableau, alors après l'instruction
P++; P pointe
sur A[i+1] P+=n;
P pointe sur A[i+n]
P--; P pointe sur
A[i-1]
P-=n; P pointe sur A[i-n]

d) Domaine des opérations :


L'addition, la soustraction, l'incrémentation et la décrémentation sur les pointeurs
sont seulement définies à l'intérieur d'un tableau. Si l'adresse formée par le pointeur et
l'indice sort du domaine du tableau, alors le résultat n'est pas défini.
Seule exception : Il est permis de 'pointer' sur le premier octet derrière un tableau (à
condition que cet octet se trouve dans le même segment de mémoire que le tableau).
Cette règle, introduite avec le standard ANSI-C, légalise la définition de boucles qui
incrémentent le pointeur avant l'évaluation de la condition d'arrêt. Exemples :
int
A[1
0];
int
*P;

P = A+9; /* dernier élément -> légal */


P = A+10; /* dernier élément + 1 -> légal */
P = A+11; /* dernier élément + 2 -> illégal */
P = A-1; /* premier élément - 1 -> illégal */

e) - Soustraction de deux pointeurs :


Soient P1 et P2 deux pointeurs qui pointent dans le même tableau :
P1-P2 fournit le nombre de composantes comprises entre P1 et P2.
Le résultat de la soustraction P1-P2 est
- négatif, si P1 précède P2 - zéro, si P1 = P2
- positif, si P2 précède P1
- indéfini, si P1 et P2 ne pointent pas dans le même tableau
Plus généralement, la soustraction de deux pointeurs qui pointent dans le même tableau
est équivalente à la soustraction des indices correspondants.

f) - Comparaison de deux pointeurs :


On peut comparer deux pointeurs par <, >, <=, >=, ==, !=.
La comparaison de deux pointeurs qui pointent dans le même tableau est équivalente à
la comparaison des indices correspondants. (Si les pointeurs ne pointent pas dans le même
tableau, alors le résultat est donné par leurs positions relatives dans la mémoire).

Vous aimerez peut-être aussi