Documente Academic
Documente Profesional
Documente Cultură
1
Programmation C/ Pointeurs
Dans cette section, nous allons prsenter un mcanisme permettant de manipuler les adresses, les pointeurs. Un
pointeur a pour valeur l'adresse d'un objet C d'un type donn (un pointeur est typ). Ainsi, un pointeur contenant
l'adresse d'un entier sera de type pointeur vers entier.
Usage et dclaration
L'oprateur & permet de connaitre l'adresse d'une variable, on dira aussi la rfrence. Toute dclaration de variable
occupe un certain espace dans la mmoire de l'ordinateur. La rfrence permet de savoir o cet emplacement se
trouve. En simplifiant l'extrme, on peut considrer la mmoire d'un ordinateur comme une gigantesque table
d'octets. Quand on dclare une variable de type int, elle sera alloue un certain emplacement (ou dit autrement :
un indice, une adresse ou une rfrence) dans cette table. Un pointeur permet simplement de stocker une rfrence, il
peut donc tre vu comme un nombre allant de 0 la quantit maximale de mmoire dont dispose votre ordinateur
(moins un, pour tre exact).
Un pointeur occupera habituellement toujours la mme taille (occupera la mme place en mmoire), quelque soit
l'objet se trouvant cet emplacement. Il s'agit en gnral de la plus grande taille directement grable par le
processeur : sur une architecture 32bits, elle sera de 4 octets, sur une architecture 64bits, 8 octets, etc. Le type du
pointeur ne sert qu' renseigner comment sont organises les donnes suivant l'adresse rfrence par le pointeur. Ce
code, par exemple, affiche la rfrence d'une variable au format hexadcimal:
int i;
printf("%p\n", &i);
Pouvoir rcuprer l'adresse n'a d'intrt que si on peut manipuler l'objet point. Pour cela, il est ncessaire de pouvoir
dclarer des pointeurs, ou dit autrement un objet pouvant contenir des rfrences. Pour cela on utilise l'toile (*)
entre le type et le nom de la variable pour indiquer qu'il s'agit d'un pointeur:
T * pointeur, * pointeur2, /* ..., */ * pointeurN;
Dclare les variables pointeur, pointeur2, ..., pointeurN de type pointeur vers le type T. noter la bizarrerie du
langage vouloir associer l'toile la variable et non au type, qui oblige rpter l'toile pour chaque variable.
/* Ce code contient une dclaration volontairement confuse */
int * pointeur, variable;
Cet exemple de code dclare un pointeur sur un entier de type int et une variable de type int. Dans un vrai
programme, il est rarement possible d'utiliser des noms aussi triviaux, aussi il est recommand de sparer la
dclaration des variables de celles des pointeurs (ou d'utiliser l'instruction typedef, qui, elle, permet d'associer
l'toile au type), la lisibilit du programme sera lgrement amliore.
Il est essentiel de bien comprendre ce qui a t dclar dans ces exemples. Chaque pointeur peut contenir une
rfrence sur un emplacement de la mmoire (un indice dans notre fameuse table). On peut obtenir une rfrence (ou
un indice) avec l'oprateur & (ou allouer une rfrence soit-mme avec des fonctions ddies, c.f la section
suivante). Cet oprateur transforme donc une variable de type T en un pointeur de type T *. Insistons sur le terme
variable, car videmment des expressions telles que '&23435' ou '&(2 * a / 3.)' n'ont aucun sens, dans la
mesure o les constantes et expressions du langage n'occupent aucun emplacement susceptible d'intresser votre
programme.
Ce code, par exemple, affiche la rfrence d'une variable dans un format dfini par l'implmentation (qui peut tre
hexadcimal, ou une combinaison "segment:offset", par exemple):
Programmation C/Pointeurs
2
int i;
printf("%p\n", &i);
Il ne faut pas oublier que, comme toutes les variables locales en C, un pointeur est l'origine non initialis. Une
bonne attitude de programmation est de s'assurer que lorsqu'il ne pointe pas vers un objet valide, sa valeur est mise
zro (ou NULL, qui est dclar entre autre dans <stdio.h>).
L'arithmtique des pointeurs
L'arithmtique associe aux pointeurs est sans doute ce qui a valu au C sa rputation d'assembleur plus compliqu
et plus lent que l'assembleur . On peut trs vite construire des expressions incomprhensibles avec les oprateurs
disponibles. Dans la mesure du possible, il est conseill de se limiter des expressions simples, quitte les
dcomposer, car la plupart des compilateurs savent trs bien optimiser un code C.
Drfrencement
Il s'agit de l'opration la plus simple sur les pointeurs. Comme son nom l'indique, il s'agit de l'opration rciproque
au rfrencement (&). L'oprateur associ est l'toile (*), qui est aussi utilis pour dclarer un type pointeur. Cet
oprateur permet donc de transformer un pointeur de type T *, en un objet de type T, les oprations affectant l'objet
point :
int variable = 10;
int * pointeur = &variable;
*pointeur = 20; /* Positionne 'variable' 20 */
Ici, pointeur contient une adresse valide, celle de variable; son drfrencement est donc possible. Par
contre, si pointeur tait une variable locale non initialise, son drfrencement provoquerait coup sr un arrt
brutal de votre programme.
Vous obtiendrez le mme rsultat, si pointeur est initialis NULL. Cette adresse est invalide et toute tentative
de drfrencement se soldera par un arrt du programme.
Arithmtique de base
L'arithmtique des pointeurs s'apparente celle des entiers, mais il est important de comprendre la distinction entre
ces deux concepts.
Les oprations arithmtiques permises avec les pointeurs sont:
Addition / soustraction d'une valeur entire un pointeur (on avance / recule d'un nombre de cases mmoires gal
la taille du type T) : le rsultat est donc un pointeur, de mme type que le pointeur de dpart.
Il faut bien faire attention avec ce genre d'opration ne pas sortir du bloc mmoire, car le C n'effectuera aucun test
pour vous. Considrez l'exemple suivant:
/* Parcours les lments d'un tableau */
int tableau[N];
int * p;
for (p = tableau; p < &tableau[N]; p ++)
{
/* ... */
Programmation C/Pointeurs
3
}
Normalement un tableau de N cases permet d'tre itr sur les indices allant de 0 N - 1, inclusivement. L'expression
&tableau[N] fait rfrence la case mmoire non alloue immdiatement aprs le plus grand indice, donc
potentiellement source de problme. Toutefois, par exception pour le premier indice aprs le plus grand, C garantit
que le rsultat de l'expression soit bien dfini. Bien sr, il ne faut pas drfrencer ce pointeur.
noter qu' l'issue de la boucle, p pointera sur la N+1me case du tableau, donc hors de l'espace allou. Le C
autorise tout fait ce genre de pratique, il faut juste faire attention ne pas dfrencer le pointeur cet endroit.
Soustraction de deux pointeurs de mme type (combien d'objet de type T y a t-il entre les deux pointeurs) : le
rsultat est donc un entier, de type ptrdiff_t. Ce code contient une erreur volontaire !
int autre_tableau[3];
int tableau[10];
int * p = &tableau[5]; /* p pointe sur le 6e lment du
tableau */
int * q = &tableau[3]; /* q pointe sur le 4e lment du
tableau */
ptrdiff_t diff1 = p - q; /* diff1 vaut 2 */
ptrdiff_t diff2 = q - p; /* diff2 vaut -2 */
q = &autre_tableau[2];
ptrdiff_t dif3 = p - q; /* Erreur ! */
Dans cet exemple, les deux premires soustractions sont dfinies, car p et q pointent sur des lments du mme
tableau. La troisime soustraction est indfinie, car on utilise des adresses d'lments de tableaux diffrents.
Notons que l'oprateur [] s'applique toujours une oprande de type entier et une autre de type pointeur. Lorsqu'on
crit tableau[i], il y a en fait une conversion de tableau pointeur avec l'application de l'oprateur []. On peut
donc bien sr utiliser l'oprateur [] avec un pointeur pour oprande:
int a;
int b;
int * p = &a; /* On peut accder la valeur de 'a' via 'p[0]' ou '*p'
*/
/* p[1] est indfini - n'esprez pas accder la valeur de b depuis
l'adresse de a */
Arithmtique avec effet de bord
C'est sans doute ce qui a donn des sueurs froides des gnrations de programmeurs dcouvrant le C : un usage
optimis de la priorit des oprateurs, le tout imbriqu dans des expressions rallonge. Par exemple 'while(
*d++ = *s++ );', pour copier une chaine de caractres.
En fait, en dcomposant l'instruction, c'est nettement plus simple qu'il ne parait. Par exemple :
int i;
int * entier;
/* ... */
i = *entier++; /* i = *(entier++); */
Programmation C/Pointeurs
4
Dans ce cas de figure, l'oprateur d'incrmentation ayant priorit sur celui de drfrencement, c'est celui-ci qui sera
appliqu en premier. Comme il est postfix, l'oprateur ne prendra effet qu' la fin de l'expression (donc de
l'affectation). La variable i sera donc tout simplement affecte de la valeur pointe par entier et aprs cela le pointeur
sera incrment. Voici les diffrents effets suivant les combinaisons de ces deux oprateurs :
i = *++entier; /* Incrmente d'abord le pointeur, puis drfrence la
nouvelle adresse pointe */
i = ++*entier; /* Incrmente la valeur pointe par "entier", puis
affecte le rsultat "i" */
i = (*entier)++; /* Affecte la valeur pointe par "entier" et
incrmente cette valeur */
On peut videmment complexifier les expressions outrance, mais privilgier la compacit au dtriment de la clart
et de la simplicit dans un hypothtique espoir d'optimisation est une erreur de dbutant viter.
Tableaux dynamiques
Un des intrts des pointeurs et de l'allocation dynamique est de permettre de dcider de la taille d'une variable au
moment de l'excution, comme par exemple pour les tableaux. Ainsi pour allouer un tableau de n entiers (n tant
connu l'excution), on dclare une variable de type pointeur sur entier laquelle on alloue une zone mmoire
correspondant n entiers :
int * alloue_tableau(int n, size_t taille)
{
return malloc(n * taille);
}
/* Ailleurs dans le programme */
int * tableau = alloue_tableau(256, sizeof *tableau);
if (tableau != NULL)
{
/* oprations sur le tableau */
/* ... */
free( tableau );
}
Cet exemple alloue un tableau de 256 cases. Bien que la variable soit un pointeur, il est dans ce cas permis d'accder
aux cases de 0 255, soit entre les adresses &tableau[0] et &tableau[255], incluses.
Tableaux dynamiques plusieurs dimensions
Tout comme on pouvait dclarer des tableaux statiques plusieurs dimensions, on peut dclarer des tableaux
dynamiques plusieurs dimensions. Pour dclarer un tel tableau, on dclare des pointeurs sur des pointeurs (etc.) sur
des types. Pour dclarer un tableau dynamique d'entiers deux dimensions :
int ** matrice;
L'allocation d'un tel objet va se drouler en plusieurs tapes (une par toile), on alloue d'abord l'espace pour un
tableau de pointeurs vers entier. Ensuite, on alloue pour chacun de ces tableaux l'espace pour un tableau d'entiers. Si
on veut une matrice 4x5 :
Programmation C/Pointeurs
5
#define LIGNES 4
#define COLONNES 5
int i;
matrice = malloc(sizeof *matrice * LIGNES);
for (i = 0; i < LIGNES; i++)
{
matrice[i] = malloc(sizeof **matrice * COLONNES);
}
Pour librer l'espace allou pour une telle structure, on procde de manire inverse, on commence par librer
chacune des lignes du tableau, puis le tableau lui mme :
for(i = 0; i < LIGNES; i++)
{
free(matrice[i]);
}
free(matrice);
Utilisation des pointeurs pour passer des paramtres par adresse
Toutes les variables en C, l'exception des tableaux, sont passs par valeurs aux paramtres des fonctions. C'est
dire qu'une copie est effectue sur la pile d'appel. Si bien que toutes les modifications de la variable effectues dans
la fonction seront perdues une fois de retour l'appelant. Or, il y a des cas o l'on aimerait bien pouvoir modifier une
variable passe en paramtre et que ces modifications perdurent dans la fonction appelante. C'est un des usages des
paramtres par adresse : permettre la modification d'une variable de l'appelant, comme dans l'exemple suivant:
#include <stdio.h>
/* Ce code change le contenu de deux variables */
void echange(int * a, int *b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int a = 5;
int b = 2;
printf("a = %d, b = %d.\n", a, b);
/* On passe 'echange' les adresses de a et b. */
echange(&a, &b);
printf("a = %d, b = %d.\n", a, b);
Programmation C/Pointeurs
6
echange(&a, &b);
printf("a = %d, b = %d.\n", a, b);
return 0;
}
Ce passage par adresse est extrmement rpandu pour optimiser la quantit de donnes qui doit transiter sur la pile
d'appel (qui est, sur beaucoup de systmes, de taille fixe). En fait, mme si la variable ne doit pas tre modifie, on
utilise quand mme un passage par adresse, juste pour viter la copie implicite des variables autres que les tableaux.
Ceci est particulirement intressant avec les structures, puisque celles-ci ont tendance tre assez imposantes, et
cela ne nuit pas trop la lisibilit du programme.
Pointeurs vers fonctions
Les pointeurs vers les fonctions sont un peu spciaux, parce qu'ils n'ont pas d'arithmtique associe (car une telle
arithmtique n'aurait pas beaucoup de sens). Les oprations permises avec les pointeurs sur fonctions sont en fait
relativement limites:
type_retour (*pointeur_fonction)(liste_paramtres);
Dclare pointeur_fonction, un pointeur vers une fonction prenant liste_paramtres comme paramtres et renvoyant
type_retour. Le parenthsage est ici obligatoire, sans quoi l'toile se rattacherait au type de retour. Pour faire pointer
un pointeur vers une fonction, on utilise une affectation normale :
pointeur_fonction = &fonction;
/* Qui est en fait quivalent : */
pointeur_fonction = fonction;
O fonction est compatible avec le pointeur (mmes paramtres et valeur de retour). Une fois que le pointeur pointe
vers une fonction, on peut appeler cette fonction :
(*pointeur_fonction)(paramtres);
/* Ou plus simplement, mais moins logique syntaxiquement */
pointeur_fonction(paramtres);
Sources et contributeurs de l'article
7
Sources et contributeurs de l'article
Programmation C/ Pointeurs Source: http://fr.wikibooks.org/w/index.php?oldid=239495 Contributeurs: Alveric, DavidL, Dhenry, Guillaumito, Marc Mongenet, Tados, Tavernier, Tpierron,
35 modifications anonymes
Source des images, licences et contributeurs
Image:Achtung.svg Source: http://fr.wikibooks.org/w/index.php?title=Fichier:Achtung.svg Licence: Public Domain Contributeurs: see below.
Licence
Creative Commons Attribution-Share Alike 3.0 Unported
http:/ / creativecommons. org/ licenses/ by-sa/ 3. 0/