Arithmétique des pointeurs

Il est tout à fait possible de modifier un pointeur, c’est-à-dire de changer l’adresse à laquelle il réfère. On dispose même pour cela d’un certain nombre d’opérateurs.

L’opérateur d’incrémentation ++ par exemple agit de la même façon que sur des entiers. Cependant, p est augmenté d’une position, non d’un octet. En effet, p pointe sur un type T qui a une certaine taille en octets t (dans le cas de int par exemple, t vaut 2 ou 4 en général) ; lorsqu’on écrit p++, dans ce cas, l’adresse qui est la valeur de p est augmentée de t octets, afin de pointer sur l’élément de type T supposé suivre dans la mémoire.

On retrouve ainsi un comportement identique à celui des tableaux. Un pointeur de type int*, par exemple, considère que la mémoire est découpée en tranches de deux octets (si le type int occupe deux octets) ; un pointeur de type long* la considère comme découpée en tranches de quatre octets, et un de type char* la voit en tranches de un octet seulement.

Ainsi, si l’on écrit :

char *pc;
int *pi = (int*) pc;
long *pl = (long*) pc;
pc++;
pi++;
pl++;

et qu’au début pc vaut 1000 par exemple, à la fin pc vaudra 1001, pi vaudra 1002, pl vaudra 1004.

Exercice 3.3

D’après les règles de précédence des opérateurs, qu’est-ce qui est affiché ici :

char s[] = "verte", *p = s;
cout << *++p;
cout << ++*p;
cout << *(p++);
cout << *p++;
Voir solution

On peut ainsi ajouter ou retrancher un entier à un pointeur ; le pointeur se déplace alors du nombre de positions indiquées. Ainsi, en exécutant à la suite des précédentes :

pl += 6;  pi -= 2;

le pointeur pl pointera six positions d’entiers longs plus loin, soit à l’adresse 1004 + 6 * 4 = 1028, tandis que pi pointera sur 1002 - 2 * 2 = 998.

Évidemment, il est aussi possible d’ajouter un entier à un pointeur, puis de placer le résultat dans un autre pointeur de même type :

pi2 = pi + 5;

Dans ce cas, comme pi vaut 998, pi2 vaudrait 1008.

De même, il est possible de retrancher deux pointeurs de même type ; dans ce cas, on obtient un entier, qui n’est pas la différence des adresses, mais la différence des positions. Par exemple la différence :

int i = pi2 - pi;

placera la valeur (1008 - 998)/2, soit 5, dans i.

Il en résulte en particulier qu’on ne peut calculer la différence de deux pointeurs de types différents, sauf en utilisant une conversion de type afin de les faire coïncider :

i = pl - pi;           // interdit types  différents
i = (int*) pl -pi;     // ok, changement de type pour pl

L’arithmétique des pointeurs est très souvent utilisée dans la pratique. Voici par exemple une implantation possible de la fonction strlen qui renvoie la longueur d’une chaîne de caractères (caractère nul final non compris) :

unsigned strlen(char *chaine)
          {
              for (char *s = chaine; *s; s++) // les chaînes se terminent par 0;
              return s -chaine;
          }

Que se passe-t-il en réalité ? Un autre pointeur char* nommé s est créé et initialisé à la même valeur que chaine. Puis il est incrémenté (s++), jusqu’à rencontrer le caractère nul (c’est-à-dire jusqu’à ce qu’on n’ait plus *s != 0, d’où la forme particulièrement simple du test : *s). La différence de valeur entre les deux pointeurs est alors égale à la longueur de la chaîne.

Exercice 3.4

Quelles modifications faudrait-il faire à la fonction précédente, si l’on voulait trouver le nombre d’éléments non nuls consécutifs dans une table d’entiers désignée par un pointeur int* ?

Voir solution
Exercice 3.5

En vous inspirant du schéma de fonction précédent, réécrivez la fonction strcmp, qui compare deux chaînes de caractères s1 et s2, et renvoie 0 si elles sont égales, une valeur < 0 si s1 < s2 (au sens lexicographique, c’est-à-dire si s1 précède s2 dans le dictionnaire), et une valeur > 0 si s1 > s2.

On supposera les caractères unsigned, comme dans la librairie <string.h>.

Voir solution
Précédent Précédent Sommaire Sommaire Suivant Suivant