Les pointeurs - Université Paris 8
Transcription
Les pointeurs - Université Paris 8
L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - Les pointeurs Le langage C permet de manipuler des adresses d’objets ou de fonctions par le biais de pointeurs. Pour ce faire on peut désigner des variables dites de type pointeur, destinées à contenir des adresses mémoires. Les pointeurs sont fortement typés, par exemple nous ne pouvons pas placer des caractères dans l’adresse mémoire pointée par un pointeur de type entier. L’utilisation de l’opérateur cast peut toutefois autoriser certaines conversions. Il nous est possible ‘effectuer, dans une certaine mesure, des opérations arithmétiques sur les pointeurs : incrémentation, décrémentation, soustraction. Ces opérations vont plus loin qu’un simple calcul d’adresse, elles prennent en compte la taille des objets pointés. Nous verrons également qu’il y a une étroite relation entre la notion de tableau et celle de pointeur. 1. La notion de pointeur La déclaration suivante : int *adr ; Précise que adr est une variable de type pointeur sur des objets de type int. 1.1. Attribuer une valeur à une variable de type pointeur Etant donné que la variable adr n’a pas encore été initialisées, alors sa valeur est pour l’instant indéfinie. Pour attribuer une valeur à adr, il faut la faire pointer sur un entier précis. Pour faire cela il existe différentes méthodes : Méthode 1 : int n ; adr = &n ; ou int tab[3] ; adr = &tab[0] ; 1 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - Méthode 3 : #include <stdlib.h> Adr = (int) malloc( sizeof(int) ); La première méthode consiste à affecter à adr l’adresse réservée à la variable n ou à l’élément d’indice 0 du tableau d’entiers tab[0]. La seconde méthode consiste à allouer dynamiquement un emplacement mémoire d’une taille égale au type qui y sera stocké. 1.2. L’opérateur * Cet opérateur sert à accéder à la valeur de l’objet pointé. Si adr est un pointeur alors *adr est l’objet pointé par adr. Exemple : int * adr; int n = 20; ad = &n; /*ad pointe sur l'entier n qui contient la valeur 20 */ printf("%d", *ad); /*affiche la valeur de 20 */ *ad = 30; printf("%d", *ad); /*affiche la valeur de 30 */ printf("%d", n); /* affiche la valeur de 30 aussi */ L’avant dernière instruction est équivalente à : n = 30; L’intérêt d’une telle utilisation peut s’avérer utile, comme dans le cas suivant : ... if (n>p) adr = &n; else adr = &p; ... *adr = 30; /* place la valeur de 30 dans n ou dans p suivant le cas */ Si l’emplacement adr avait été alloué dynamiquement alors l’on pourrait affecter une valeur à l’adresse mémoire réservée de la même façon : adr = malloc(sizeof(int)) ; *adr = 30; 1.3. Exemples de déclarations de pointeurs Déclaration de pointeurs sur des objets de type de base ou de type structure, ou défini par typedef. 2 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - - unsigned int n, *ptr, q, *adr; - adr et ptr sont des pointeurs sur des éléments de type unsigned int Il est nécéssaire de répéter le symbole * dans chaque déclarateur de pointeur. ps est un pointeur sur des structures de type struct point. s est une structure de type struct point. - vecteur est synonyme de int[3] pv est un pointeur sur des tableaux de 3 int - struct point { char nom; int x, y; }; struct point *ps, s; typedef int vecteur[3]; vecteur *pv; pointeur de pointeur float x, *pf, **ppf; int (**chose)[10]; int *pt[10]; int **ppt[10]; int *f(float x); - x est un float et pf est un pointeur sur un float **ppf est un float *ppf est un pointeur sur un float ppf est un pointeur sur un pointeur sur un float (* *chose)[10] est un int (* *chose) est un tableau de 10 int *chose est un pointeur sur un tableau de 10 int chose est un pointeur de pointeur sur un tableau de 10 int Attention à bien mettre les parenthèses ici. Sans les parenthèses l’interprétation est différente comme nous pouvons le voir dans la suite. - *pt[10] est un int - pt[10] est un pointeur sur un int - pt est un tableau de 10 pointeurs sur un int - **ppt[10] est un int - *ppt[10] est un pointeur sur un int - ppt[10] est un pointeur sur un pointeur sur un int - ppt est un tableau de 10 pointeurs sur un pointeur sur un int - *f(float x) est un int - f(float x) est un pointeur sur un int - f est une fonction recevant un float et retournant un pointeur sur un int 2. Les propriétés des pointeurs 2.1. Opérations entre pointeurs Addition ou soustraction d’un entier à un pointeur : Si nous déclarons un pointeur comme ci-dessous : int * adr; 3 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - alors l’expression adr + 1 va retourner un pointeur sur un int et sa valeur est l’adresse de l’entier suivant l’entier pointé par adr. On pourra relever que la différence entre l’adresse de pn et pn+1 est de 4. Cette valeur correspond au nombre d’octets d’un int. pn + 1 ne modifie pas le contenu de pn. Par contre l’appel à pn++ va faire pointer pn vers l’adresse (pn+1) et dans ce cas le contenu de pn vas changer. De la même façon : pn+3 permet de pointer sur un int qui se troive trois adresses plus loin que celle contenue dans pn. Donc attention à l’emploi de cette opération !! /!\ pn++ != (pn+1) Soustraction entre deux pointeurs : Il est possible de soustraire les valeurs de deux pointeurs de même type. Comme nous l’avons vu précédemment, en additionnant un entier n à un pointeur pn cela nous positionne sur la nième adresse devant pn. Soit par exemple les pointeurs suivants : int *pn, *pm; pm = pn + 4; printf("%d", pn - pm); /* va afficher 4 */ pm – pn va retourner 4 qui correspond au nombres d’adresses mémoires contenant un int entre pm et pn. De même dans le cas suivant : int t[10]; int *pn = &t[3]; int *pm = &t[8]; pm - pn /* va retourner 5 */ Il y a bien 5 éléments de type int séparant t[3] et t[8]. 4 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] 2.2. - Cours 6 - Lien entre pointeurs et tableaux Tableau à une dimension : Soit un tableau tab de int et un pointeur pn sur un int déclarés comme suit : int tab[10], *pn; Pour accéder au nième élément du tableau, on fait appel à tab[n]. Maintenant faisons pointer pn sur le premier élément du tableau tab : pn = &tab[0]; /* ou pn = &tab; ce qui est équivalent */ Pour accéder au nième élément du tableau, il suffit d’écrire : tab[n] ou *(pn + n) Exemple : int *pn, tab[5]={4,2,7,0,6}; printf("%d\n", tab[3]); /* affiche 0 */ pn = &tab; printf("%d\n", *(pn+3)); /* affiche 0 aussi */ Ainsi, déclarer un tableau revient à déclarer un pointeur sur un int. Comme nous venons de voir pour accéder au nième élément du tableau : - soit nous faisons appel au pointeur pn : *(pn+3) - soit nous utilisons les indices du tableau : tab[n] - soit nous utilisons directement le tableau comme un pointeur : *(tab+3) Donc t[i] est équivalent à *(t+i). De même *(pn+i) équivaut à pn[i] même si pn est de type pointeur il nous est possible d’accéder au i ème élément de même type avec l’emploi des indices, tout comme pour les tableaux. Tableau à deux dimensions : L’expression t[i][j] s’interprète comme (t[i])[j], ce qui peut se traduire par : T[j] <-> *(T+j) <-> *( *(t+i) +j) Pour résumer, nous avons : &t[0] <-> t &t[i] <-> t+i t[1] <-> *(t+1) t[i] <-> *(t+i) t[i][0] <-> *t[i] <-> **(t+i) t[i][j] <-> (t[i])[j] <-> ( *(t+i))[j] <-> *( *(t+i) + j) 5 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - 3. Le pointeur NULL et le pointeur générique void * Le pointeur NULL : Le symbole NULL du langage C permet d’initialiser un pointeur qui ne pointe sur rien. Cette valeur NULL peut être affectée à tout pointeur quel que soit sont type. Ce symbole est défini dans les librairies stdio.h, stdlib.h. int *pn = NULL ; En utilisant le pointeur NULL, cela nous permet alors de comparer si deux pointeurs sont égaux (opérateur ==) ou non égaux ( !=) entre eux. Par contre les comparaisons d’inégalité (<,>,>=, >=) ne sont pas autorisés avec les pointeurs sauf si l’on manipule des tableaux. Dès que l’on déclare une variable de type pointeur, il est préférable de l’initialiser à NULL, surtout dans le cas oùu l’on ne connait pas sa valeur initiale au début du programme. Au moment d’utiliser un pointeur, il est préférable de tester si sa valeur est bien non nulle : if(pn != NULL) *pn = ... Equivalent à : if(pn) *pn = ... Cela s’avèrera utile pour la gestion des listes chaînées. Le pointeur générique : Nous avons vu jusqu’à maintenant qu’un pointeur pointe sur un objet typé. Or il est possible de déclarer un pointeur sans type. Comme pour les déclarations de fonction, il nous est possible de déclarer des fonctions sans type. Particulièrement lorsqu’elles ne renvoient aucun résultat. Il nous est donc aussi possible de déclarer des pointeurs sans type particulier. Dans ce cas ce sera l’adresse contenu dans le pointeur qui aura un sens. Ceci est particulièrement utile lorsque : - Une fonction doit traiter des objets de différents types, alors qu’elle a reçu l’adresse de l’objet en argument. - L’on souhaite seulement échanger les valeurs de deux pointeurs. Un pointeur de type générique se déclare comme suit : void * pn; void *pn, *pm, n; pn++; pn = pn + 5; int i = pm - pn; int *pi; if (pn != pi) if (pn == pi) /* /* /* /* incorrect, n ne peut être de type void*/ interdit */ l'expression p + 5 est interdite */ l'expression pm -pn est interdite */ /* correct */ /* correct */ 6 L1 Informatique – Université Paris 8 - 2010-2011 Programmation Impérative I Rim Chaabane [email protected] - Cours 6 - 4. Le cast d’un pointeur Exemple : char * pc ; int * pi ; pi= (int *) pc ; ou pc = (char *) pi ; 7