Section courante

A propos

Section administrative du site

Pointeurs et tableaux

Un pointeur est une variable contenant l'adresse d'une variable. Les pointeurs sont très utiles en C, en partie parce qu'ils sont parfois le seul moyen d'exprimer un calcul, et en partie parce qu'ils conduisent généralement à un code plus compact et plus efficace que ce qui peut être obtenu par d'autres moyens. Les pointeurs et les tableaux sont étroitement liés. Les pointeurs ont été regroupés avec l'instruction goto comme un moyen merveilleux de créer des programmes impossibles à comprendre. Il est certainement vrai lorsqu'ils sont utilisés avec négligence, et il est facile de créer des pointeurs pointant vers un endroit inattendu. Avec le déplacement, cependant, les pointeurs peuvent également être utilisés pour obtenir clarté et simplicité. Le principal changement dans ANSI C est de rendre explicites les règles sur la façon dont les pointeurs peuvent être manipulés, en imposant en fait ce que les bons programmeurs pratiquent déjà et les bons compilateurs appliquent déjà. De plus, le type void * (pointeur vers void) remplace char * comme type approprié pour un pointeur générique.

Pointeurs et adresses

Commençons par une image simplifiée de l'organisation de la mémoire. Une machine typique a un réseau de cellules de mémoire numérotées ou adressées consécutivement qui peuvent être manipulées individuellement ou en groupes contigus. Une situation courante est que n'importe quel octet peut être un char, une paire de cellules d'un octet peut être traitée comme un entier short et quatre octets adjacents forment un long. Un pointeur est un groupe de cellules (souvent deux ou quatre) pouvant contenir une adresse. Donc, si c est un char et ptr est un pointeur pointant vers lui, nous pourrions représenter la situation de cette façon :

L'opérateur binaire & donne l'adresse d'un objet, donc l'instruction :

  1. ptr = &c;

assigne l'adresse de c à la variable ptr, et ptr est dit pointeur vers c. L'opérateur & s'applique uniquement aux objets en mémoire : variables et éléments de tableau. Il ne peut pas être appliqué aux expressions, aux constantes ou aux variables register. L'opérateur binaire * est l'opérateur d'indirection ou de déréférencement; lorsqu'il est appliqué à un pointeur, il accède à l'objet sur lequel pointe le pointeur. Les premières choses à faire avec les pointeurs sont de déclarer une variable de pointeur, de la définir pour qu'elle pointe quelque part, et enfin de manipuler la valeur vers laquelle elle pointe. Une simple déclaration de pointeur ressemble à ceci :

  1. int *ip;

Supposons que x et y sont des entiers et que ip est un pointeur vers int. Cette séquence artificielle montre comment déclarer un pointeur et comment utiliser & et * :

  1. int x=1, y=2, z[10];
  2. int *ip;
  3. ip = &x, y = *i, *ip = 0, ip = &z[0];

Les déclarations de x, y et z sont ce que nous avons vu depuis le début. La déclaration de l'ip du pointeur :

  1. int *ip; 

est conçu comme un mnémonique; il dit que l'expression *ip est un int. La syntaxe de la déclaration d'une variable imite la syntaxe des expressions dans lesquelles la variable peut apparaître. Ce raisonnement s'applique également aux déclarations de fonction. Par exemple :

  1. double *dp, atof(char *); 

indique que dans une expression *dp et atof(s) ont des valeurs de type double, et que le paramètre de atof est un pointeur vers char. Vous devez également noter l'implication qu'un pointeur est contraint de pointer vers un type particulier d'objet : chaque pointeur pointe vers un type de données spécifique. (Il y a une exception : un pointeur vers void est utilisé pour contenir tout type de pointeur mais ne peut pas être déréférencé lui-même. Si ip pointe vers l'entier x, alors *ip peut se produire dans n'importe quel contexte où x pourrait, donc :

  1. *ip = *ip + 10; 

incrémente *ip de 10. Les opérateurs binaires * et & se lient plus étroitement que les opérateurs arithmétiques, donc l'affectation :

  1. y = *ip + 1; 

prend tout ip pointe vers, ajoute 1 et attribue le résultat à y, tandis que :

  1. *ip += 1; 

incrémente ce vers quoi pointe ip, comme le font :

  1. ++*ip; 

et

  1. (*ip)++; 

Les parenthèses sont nécessaires dans ce dernier exemple; sans eux, l'expression incrémenterait ip au lieu de ce qu'elle pointe, car les opérateurs binaires comme * et += s'associent de droite à gauche. Enfin, comme les pointeurs sont des variables, ils peuvent être utilisés sans déréférencement. Par exemple, si iq est un autre pointeur vers int :

  1. iq = ip; 

copie le contenu de ip dans iq, faisant ainsi pointer iq vers tout ip pointé.

Pointeurs et paramètres de fonction

Puisque C passe des paramètres aux fonctions par valeur, il n'y a aucun moyen direct pour la fonction appelée de modifier une variable dans la fonction appelante. Par exemple, une routine de tri peut échanger deux éléments dans le désordre avec une fonction appelée swap. Il ne suffit pas d'écrire :

  1. swap(a,b); 

où la fonction swap est définie comme

  1. void swap(int x,int y) {
  2.  int tmp = x;
  3.  x = y;
  4.  y = tmp;
  5. }

En raison de l'appel par valeur, swap ne peut pas affecter les paramètres a et b dans la routine l'ayant appelé. La fonction ci-dessus n'échange que les copies de a et b. La manière d'obtenir l'effet souhaité est que le programme appelant passe des pointeurs vers les valeurs à modifier :

  1. swap(&a,&b); 

Puisque l'opérateur & produit l'adresse d'une variable, &a est un pointeur vers a. Dans la fonction swap lui-même, les paramètres sont déclarés comme des pointeurs et les opérandes sont accessibles indirectement via eux.

  1. void swap(int *x,int *y) {
  2.  int tmp = *x;
  3.  *x = *y;
  4.  *y = tmp;
  5. }

Pointeurs et tableaux

En C, il existe une relation forte entre les pointeurs et les tableaux, suffisamment forte pour que les pointeurs et les tableaux soient discutés simultanément. Toute opération pouvant être réalisée par l'indice de tableau peut également être effectuée avec des pointeurs. La version du pointeur sera en général plus rapide mais, au moins pour les non-initiés, un peu plus difficile à comprendre. La déclaration :

  1. int a[10]; 

définit un tableau a de taille 10, c'est-à-dire un bloc de 10 objets consécutifs nommés a[0], a[1], ..., a[9].

La notation a[i] fait référence au i-ème élément du tableau. Si pa est un pointeur vers un entier, déclaré comme :

  1. int *pa; 

puis puis affectation :

  1. pa = &a[0]; 

définir pa pour qu'il pointe sur l'élément zéro de a; autrement dit, pa contient l'adresse d'un a[0].

Maintenant l'assignement :

  1. x = *pa; 

copiera le contenu d'un [0] dans x.



Dernière mise à jour : Dimanche, le 8 novembre 2020