Beruflich Dokumente
Kultur Dokumente
En C, les pointeurs jouent un rle primordial dans la dfinition de fonctions. Ils prsentent un moyen, entre autres, de changer le contenu de variables dclares dans d'autres fonctions. Ainsi le traitement de tableaux et de chanes de caractres dans des fonctions serait impossible sans l'utilisation de pointeurs. En outre, les pointeurs nous permettent d'crire, avec une certaine discipline, des programmes clairs, simples, compacts, efficients et fournissent souvent la seule solution raisonnable un problme. Ils permettent par exemple deffectuer les appels par rfrence, de manipuler des structures de donnes dynamiques comme les listes, les arbres, etc. et finalement dallouer dynamiquement de la place mmoire. Cependant, il faut tre trs attentif en utilisant cet outil puissant, car les pointeurs ne doivent pas tre employs ngligemment. En effet, il est assez facile de crer des pointeurs qui pointent n'importe o et malheureusement parfois sur des zones mmoires utilises par le systme.
i 35 5A0F3
Un pointeur contenant ladresse dune variable, donc pointant vers celle-ci Si un pointeur p contient l'adresse d'une variable i , on dit que 'p pointe sur i'.
65
Remarque Les pointeurs et les noms de variables ont le mme rle: Ils donnent accs un emplacement dans la mmoire interne de l'ordinateur. Il faut quand mme bien faire la diffrence: Un pointeur est une variable qui peut pointer sur diffrentes adresses. Le nom d'une variable reste toujours li la mme adresse. Il faut signaler que lon parle parfois de pointeur dont la valeur est constante (adresse constante). Par exemple, les noms des tableaux sont des pointeurs constants quivalents ladresse de la premire composante du tableau concern.
/* dclaration : p est un pointeur pointant sur un entier */ /* initialisation du pointeur p, affecte l'adresse de la variable i. p pointe maintenant vers la zone mmoire o est stocke la variable i*/ /*affiche la valeur pointe par p, donc 35*/ Introduction au Langage de Programmation C
Mohammed BENJELLOUN
66
p contient ladresse de la variable i alors que *p contient le contenu de la variable, donc 35. On peut donc accder la valeur de la variable en passant par son adresse et en utilisant loprateur contenu de. Remarque : Comme pour les autres variables, la valeur dune variable pointeur est indfinie lors de la dclaration et le reste tant quon ne lui affecte pas explicitement une valeur. Dans la reprsentation schmatique, nous pouvons illustrer le fait que 'p pointe sur i' par une flche: p: 5A0F3 35 :i
Il est mme possible de modifier les valeurs pointes. Les instructions suivantes donneront par exemple: int i = 35, j; int *p, *q; p = &i; j = *p; *p = 10; i= 20; (*p)++; q = p; i=50 ; *(q+1)= 29;
/* p=&i = 0x0012ff7c et *p = 35 */ /* j = 35 */ /* *p=i=10 */ /* i=*p=20 */ /* est quivalent i++, donc i=*p= 21 */ /* p et q pointent maintenant ensemble vers la mme adresse 0x0012ff7c */ /* i=*p=*q= 50 */ /* on range 29, 4 cases mmoire plus loin *p=*q=i reste 50 */
- p pointe sur i, - le contenu de i (rfrenc par *p) est affect j, et - le contenu de i (rfrenc par *p) est mis 10, - le contenu de p est mis 20, - le contenu de p est mis 21, - le contenu de q est mis 21. - p et q pointent vers la mme adresse et leur contenu est gal 21. - le contenu de p et de q est mis 50. - le contenu de p et de q nest pas chang par *(q+1)= 29; . Les oprateurs * et & ont la mme priorit que les autres oprateurs unaires (la ngation !, l'incrmentation ++, la dcrmentation --). Dans une mme expression, les oprateurs unaires *, &, !, ++, -- sont valus de droite gauche. Nous conseillons au programmeur de faire attention lors de lutilisation des oprateurs unaires ++ et --, notamment avec les pointeurs. Ainsi *p++ est diffrent de (*p)++.
Mohammed BENJELLOUN
67
Comme les oprateurs unaires * et ++ sont valus de droite gauche, sans les parenthses, le pointeur p serait incrment, donc ne pointerait plus sur i, mais sur une valeur peut-tre inconnue de lutilisateur.
/* affiche 5 /* affiche 5
10 */ 10 */
donneront le mme affichage avant et aprs l'appel la fonction echange, c'est--dire que celle-ci n'aura pas fait son boulot!
Mohammed BENJELLOUN
Introduction au Langage de Programmation C
68
Utiliser linstruction return peut ventuellement nous sortir une valeur, mais pas les deux. Nous avons vu cependant un exemple o les paramtres sont modifiables: il s'agit des tableaux. En fait, ceux-ci sont passs par l'adresse de leur composante 0, comme nous le verrons par la suite. Contrairement au passage par valeur, le passage par rfrence ou par adresse, n'effectue pas de copie du paramtre: il passe une rfrence sur celui-ci. En consquence, une fonction passant des paramtres par rfrence ne pourra jamais prendre (en paramtre) une constante, mais uniquement une variable. Ce mode de passage peut donc tre intressant, surtout si un paramtre est important en terme de taille (un tableau, une structure, ...). Cest le mode quil faut utiliser si nous voulons modifier les paramtres dune fonction. Ceci se fait en C de manire explicite, en utilisant les pointeurs. Le programme suivant nous donne l'criture correcte de notre fonction echange. Son appel dans la fonction main affiche les rsultats escompts.
#include <stdio.h> void echange (int *pa, int *pb){ int tmp=*pa; *pa = *pb; *pb = tmp; printf("%d %d\n", *pa, *pb); } void main(){ int x=5, y=10; printf("%d %d\n",x,y); echange ( &x, &y); printf("%d %d\n",x,y); } // affiche 10 5
// affiche 5 // affiche 10
10 5
Programme 6.1 Nous attirons lattention sur la manire dcrire len-tte de la dfinition et lappel de la fonction : Dfinition : void echange (int *pa, int *pb) Appel : echange ( &x, &y); Lappel de la fonction seffectue en lui passant les adresses ou les rfrences des variables. A travers ces rfrences, ce sont les objets dsigns par x et y qui seront manipuls dans la fonction et qui seront donc modifis. C'est d'ailleurs pour cela que nous avions utilis l'criture : scanf ("%d", &n); pour lire une variable entire n. On saisit ici le contenu de l'adresse &n c'est--dire lentier n lui-mme. On peut donc aussi procder ainsi: TYPE *adr; scanf("%Desc_TYPE",adr); On saisit ici le contenu de l'adresse adr.
Mohammed BENJELLOUN
Introduction au Langage de Programmation C // TYPE: int, float, char, ... // Desc_TYPE : d, f, c,
69
Programme 6.2 : Cet exemple a t trait auparavant dans le chapitre consacr aux fonctions (Programme 5.2). Maintenant que nous connaissons les pointeurs, ce programme devient :
#include <stdio.h> void Modifier(int *v); void main(void) { int v = 5; Modifier(&v); printf("main: var = %d\n", v); } void Modifier(int* v) { *v *= 100; printf("Modifier: *v = %d son adresse=%p\n", *v, v); son Modifier*/ }
// Variable locale main // Appel de la fonction Modifier /* Affiche la valeur de v aprs passage dans la fonction Modifier*/
donc la modification effectue par la fonction Modifier est transmise main. Il faut noter aussi la manire dafficher une adresse (%p ou %X) : printf("Modifier: *v = %d son adresse=%p\n", *v, v); affiche Modifier: *v = 500 son adresse = 0012FF7C printf("Modifier: *v = %d son adresse=%X\n", *v, v); affiche Modifier: *v = 500 son adresse = 12FF7C
Mohammed BENJELLOUN
70
6.5.1. Oprateur d'indexation La smantique de l'oprateur d'indexation consiste dire qu'aprs les dclarations : int T[N]; int i; T[i] est quivalent *(T + i). T est une constante reprsentant ladresse de llment T[0] (T=&T[0]). Lexpression *T dsigne T[0]. Laccs un lment du tableau peut se faire non seulement par le nom du tableau accompagn dun indice, mais aussi par un pointeur manipul par des oprations spcifiques de pointeurs. Si le pointeur p repre l'lment d'indice i du tableau T, p + j (de mme p-j) est une valeur de type pointeur qui repre l'lment d'indice i + j (i j) du tableau T (en supposant qu'ils existent). Si on a : #define N 100 int T[N]; int * p ; p = &T[0];
/* p repre le premier lment de T*/
Dans linstruction ci-dessus, remarquez que nous rangeons dans p ladresse du premier lment du tableau. Il aurait t incorrect dcrire : p = &T ; car T est un nom du tableau et est vu par le compilateur comme une constante ; alors que loprateur & sapplique une variable pour en fournir ladresse. En revanche, nous aurions pu crire : p=T ; L'expression p + N est valide, mais p - 1 et p + N + 1 ne le sont pas. Aprs les dclarations int T[10]; int * p; on peut crire p = &T[4]; et utiliser l'oprateur d'indexation sur p, p[0] tant T[4], p[1] tant T [5], etc. p peut donc tre utilis comme un sous-tableau de T.
Mohammed BENJELLOUN
Introduction au Langage de Programmation C
71
Voici encore un autre exemple de manipulation de pointeurs : #define N 10 int T[N]; int *p,*q,*r,*s; p = &T[0]; q = p + (N-1); r = &T[N-1]; s = r - (N-1);
/* p repre le premier lment de T quivalent p=T */ /* q repre le dernier lment de T */ /* r repre le dernier lment de T */ /* s repre le premier lment de T */
Programme 6.3:
#include <stdio.h> void main() { int T[6] = { 2, 6, 4, 5, 10, 8 }; int *p; p = T; printf("%d\n", *p); p++; printf("%d\n", *p); p += 4; printf("%d\n", *p); }
Mohammed BENJELLOUN
72
Programme 6.4 :
#include <stdio.h> void main(){ int T[6] = { 2, 4, 6, 8, 10, 12 }; int *p, i; printf("T = %p, &T[0] = %p\n", T, &T[0]); p =T; printf( "for1 ...\n"); for (i=0; i<6 ; i++){ printf("p+%d = %p, p[%d]= %3d *(p+%d) = %3d\n", i,p+i, i, p[i], i, *(p+i)); } printf( "END for1 ...\n"); *p++ = 15; printf( "for2 ...\n"); for (i=0; i<6 ; i++){ printf("T[%d]=%3d,p+%d = %p, p[%d]= %3d *(p+%d) = %3d\n", i, T[i], i,p+i, i, p[i], i, *(p+i)); } printf( "END for2 ...\n"); }
for2 ... T[0]= 15, p+0 = 0012FF6C, p[0]= 4 T[1]= 4, p+1 = 0012FF70, p[1]= 6 T[2]= 6, p+2 = 0012FF74, p[2]= 8 T[3]= 8, p+3 = 0012FF78, p[3]= 10 T[4]= 10, p+4 = 0012FF7C, p[4]= 12 T[5]= 12, p+5 = 0012FF80, p[5]= 1245120 END for2 ...
6.5.2. Passage de tableau comme pointeur en paramtre Du fait de la conversion d'un identificateur de type tableau en l'adresse du premier lment, lorsqu'un tableau est pass en paramtre effectif, c'est cette adresse qui est passe en paramtre. Le paramtre formel correspondant devra donc tre dclar comme tant de type pointeur. Le passage dun tableau comme paramtre, peut tre dclar soit comme tableau, soit comme pointeur. Les dfinitions suivantes sont quivalentes :
Mohammed BENJELLOUN
Introduction au Langage de Programmation C
73
void Fonction(int Tab[], int dim){ } void Fonction(int *p, int dim){ } Examinons une fonction dont l'objet est de calculer la somme des composantes d'un vecteur. Nous pouvons crire classiquement cette fonction comme: int somvec ( int n, int x [] ){ int s = 0; int i; for (i=0; i<n; i++) s += x[i]; return s; } Nous pouvons galement, de manire rigoureusement quivalente, l'crire comme: int somvec ( int n, int *x ){ int s = 0; int i; for (i=0; i<n, x++; i++) s += *x; return s; } Si nous avons un vecteur entier x[5] aux valeurs successives 1,2,3,4,5, les appels: somvec ( 5, x); somvec ( 5, &x[0]); somvec (4, &x[1]); somvec (4, x); seront tous corrects, et donneront comme retour les valeurs respectives 15, 15, 14 et 10. Ce qui ne serait par contre pas correct, ce sont les appels somvec (5, &x); somvec (5, x[0]);
//adresse de l'adresse de la composante 0! // passage de la valeur de x[0] et non de son adresse
Ces expressions par pointeur dans l'criture de fonctions procdant sur des tableaux seront fortement exploites avec l'utilisation des chanes de caractres, comme nous allons le voir par la suite. Remarque : On a vu que lon peut remplacer par exemple void Fonction(int Tab[], int dim) par void Fonction(int *p, int dim)
Mohammed BENJELLOUN
74
Cependant, cela peut prsenter un inconvnient. En effet, lorsqu'on lit l'en-tte de cette fonction, il n'est pas possible de savoir si le programmeur a voulu passer en paramtre un pointeur vers un int (c'est--dire un pointeur vers un seul int), ou au contraire sil a voulu passer un tableau, c'est--dire un pointeur vers une zone de plusieurs int.
6.5.3. Pointeurs et chanes de caractres Les chanes constantes de caractres sont, comme on lavait dj vu, un cas particulier de tableaux char et possdent une adresse mmoire. Laffectation suivante : char *p= "Hello world"; (equivalent char *p; p="Hello world";) signifie que lon dfinit une variable pointeur p de type char* laquelle on affecte, comme valeur initiale, non pas la chane "Hello world", mais ladresse de cette dernire. Nous pouvons reprsenter p par ce graphique: p 'H' 'e' 'l' 'l' 'o' ' ' 'w' 'o' 'r' 'l' 'd' '\0'
Les instructions suivantes montrent comment afficher toute la chane de caractres en partant de p : char * p = "Hello world"; for (p = p; *p != '\0'; p++) { printf("%c ", *p); // Affiche : H e l l o w o r l d } Nous conseillons de n'utiliser ce type de dclaration char *p= "Hello world"; que pour des chanes constantes, qui ne seront pas modifies en cours de programme. Cela signifie que la fonction suivante (par exemple) provoquera une erreur l'excution sur certaines machines : p[0] = toupper(p[0]);
//fonction toupper : conversion des minuscules ! majuscules
L'utilisation du mot-cl const permet de dtecter cette erreur la compilation : const char *p= "Hello world"; En plus si p change de valeur, le tableau de dpart ne sera plus accessible, et donc la mmoire qu'il occupait serait perdue. char * p = "Hello world"; p= "Salut"; while (*p != '\0'){ printf("%c ", *p++); }
// Affiche : S a l u t
Mohammed BENJELLOUN
75
Voici un autre exemple o laffectation dune nouvelle valeur un pointeur sur une chane de caractres constante, fait perdre la chane constante initiale. D'autre part, un pointeur sur char a l'avantage de pouvoir pointer sur des chanes de n'importe quelle longueur: char * p = "Hello world"; char * q= "Salut"; q=p ; //(1) //(2) //(3)
'r'
'r'
Les affectations discutes ci-dessus ne peuvent pas tre effectues avec des tableaux de caractres. En effet, les chanes de caractres dclares comme pointeurs peuvent tre initialises avec =, affectes avec =. Lorsquelles sont dclares comme tableaux, elles peuvent tre initialises avec =, mais pas affectes avec =. Les instructions suivantes provoquent une erreur la compilation : char tab[] = "Hello world"; tab= "Salut"; Dans ce cas, le compilateur alloue un tableau de 11 caractres qu'il initialise avec les caractres H, e, l, l, o, , w, o, r, l, d, et \0. Toute occurrence de tab sera convertie en type pointeur vers char. Laffectation dans ce cas doit tre effectue par la fonction que lon a dj vue strcpy. Le code suivant corrige lerreur : char tab[] = "Hello world"; strcpy(tab,"Salut"); Afficher ce qui se trouve dans le tableau tab, peut tre ralis, par : soit : printf("%s\n", tab) ; soit : puts(tab) ; soit : for (i = 0; tab[i] != '\0'; i++) { printf("%c", tab[i]); }
Mohammed BENJELLOUN
76
Remarque : NULL est une constante symbolique de type pointeur qui joue le rle d'adresse indfinie. Cela sert pour tester si un pointeur pointe vers un objet ou pas. NULL est gale 0 dans <stdio.h>. Lorsqu'on veut prciser qu'un pointeur p ne pointe sur rien, on lui affecte la valeur particulire NULL : p=NULL; Il ne faut jamais tenter d'accder une zone mmoire inaccessible, car cela peut amener des erreurs l'excution, comme dans le code suivant : int *p=NULL; printf("%d\n",*p); // Erreur l'excution : Segmentation fault. L'accs l'adresse NULL est toujours interdit.
Mohammed BENJELLOUN
77
void free(void *ptr) : cette fonction libre la zone mmoire pointe par ptr, dans le cas o cette zone mmoire a t alloue dynamiquement par un appel la fonction malloc. On utilisera en principe cette fonction autant de fois que la fonction malloc. Si ptr pointe sur le dbut d'une zone mmoire alloue dynamiquement, free(ptr) libre cette zone mmoire. Il ne faut jamais librer une zone mmoire dj libre. Le systme pourrait alors gnrer des erreurs de nature imprvisible. Pour pouvoir utiliser les fonctions malloc et free, il est ncessaire dinclure le fichier d'en-tte standard stdlib.h : #include <stdlib.h> En plus de la fonction malloc, il existe dautres fonctions pour rserver de la mmoire. Il sagit de : char * calloc( unsigned N, unsigned taille_type); Rserve N lments de taille_type octets chacun. Elle retourne un pointeur sur le premier octet ainsi allou ou NULL si lallocation na pu tre satisfaite. L'espace allou est initialis 0. char *s; s = (char *)calloc(250, sizeof(char));
void * realloc( void *block, unsigned taille); Cette fonction permet de changer la taille affecte au bloc de mmoire fourni par un prcdent appel malloc() ou calloc(). Le programme suivant nous donne un aperu sur la manire dallouer et de librer la mmoire:
#include <stdio.h> #include <stdlib.h> #define alloue(nb,type)
(type *)malloc(nb*sizeof(type))
void main(){ float *adr1; int *adr2 adr1 = alloue(4, float); adr2 = alloue(10, int); *adr1 = -37.28; *adr2 = 123; printf("adr1 = %p adr2 = %p r1 = %f r2 = %d\n",adr1,adr2,*adr1,*adr2); free(adr1); // Libration de la mmoire free(adr2); }
Programme 6.5
Mohammed BENJELLOUN
78
Ce quil faut au moins retenir : Quand une fonction1 appelle une autre fonction2 et que la premire a besoin des modifications des variables passes en paramtres de la seconde, il faut passer les paramtres par adresse. Ceci se fait de manire explicite, en utilisant les pointeurs. Quand le paramtre de la fonction est un pointeur, la dclaration et lappel de la fonction, en comparaison un paramtre traditionnel, se font comme suit : dclaration : func(int x) func(int *px) appel : func( y) func(&y) Attention aux erreurs suivantes :
int i = 35; int *p; p = &i; //OK printf ("%d",*p); int *pt=NULL; printf("%d\n",*pt); int i = 35; int *p; *p = i; //Erreur printf ("%d",*p);
(*p)++
est diffrent de
*p++
Pour rserver de la mmoire un pointeur, on utilise par exemple la fonction malloc. Dans un programme, il faut autant de fonctions free que de fonctions malloc.
Exercices
6.1. Les oprations daffectation suivantes sont-elles correctes ?
a. b. c. d. e. f. int a,*p ; a=*p ; *p=&a; *p=*(&a); a=p; p=&a; a=(int)p;
Mohammed BENJELLOUN
Chap. 6: Les pointeurs printf(" *p=0 x=? = %d\n" , x); *p++; *p=20; printf(" *p++ x=? = %d\n" , x); }
79
6.3. Que faut-il ajouter ce programme pour quil puisse fonctionner correctement ?
void main() { int *p ; *p = 10; printf(" *p = %d\n" , *p); }
6.4. Transformer lexercice 5.3. en remplaant les fonctions MIN et MAX par une seule fonction MIN_MAX qui dtermine le minimum et le maximum dun vecteur de nombres rels entrs au clavier. Ces deux valeurs doivent tre transmises au main. 6.5. Ecrire la fonction Crypt de cryptage d'un caractre pour que le programme suivant puisse : dcaler chaque lettre de Tab de 5, ainsi un a sera remplac par un f, un b par un g, etc. On ne cryptera que les lettres majuscules et minuscules sans toucher ni la ponctuation ni la mise en page. On supposera que les codes des lettres se suivent de a z et de A Z.
void main() { char *p, Tab[80]; printf("\n Une phrase ...: "); gets(Tab); p=Tab; while(*p) Crypt(p++); printf("\nResultat :"); printf(Tab); }
Exemple : Les sanglots longs des violons de l'automne Deviendra : Qjx xfslqtyx qtslx ijx antqtsx ij q'fzytrsj ...
6.6. Complter le programme suivant qui saisit 10 entiers et les range partir de l'adresse adr_deb. Rechercher le maximum, l'afficher ainsi que son adresse.
#define imax 10 void main() { int *adr_deb, *adr_max, max; adr_deb=(int*)malloc(imax *sizeof(in));
. .
printf("Le maximum:%d, Son adresse:%p\n",max, adr_max);
.
}
Mohammed BENJELLOUN