next up previous contents index
suivant: Pointeurs et tableaux monter: main précédent: Pointeurs   Table des matières   Index

L'opérateur & et le passage par adresse

C++, à la différence de Pascal, Fortran, CAML et Java, dispose d'une opération qui permet d'obtenir l'adresse d'un objet (ou d'une fonction). Pour toute expression $E$ qui désigne un objet (ou toute expression gauche, voir § 5), &$E$ est une expression dont la valeur est l'adresse de l'objet désigné par $E$. Si $E$ est de type $T$, alors &$E$ est une expression de type $T$*. Notons la simplification suivante : &*p en p, si p est d'un type pointeur.


\begin{figurette}
% latex2html id marker 4777\begin{center}
\leavevmode
\fb...
...itialisation d'un pointeur par une adresse d'objet} \end{center} \end{figurette}

Cette opération est essentielle en C pour simuler le passage des arguments par référence, et beaucoup moins utilisée en C++, qui dispose de ce mode. Si n est une variable (ou plus généralement une expression qu'on peut placer à gauche d'une affectation) que l'on veut faire modifier par une fonction f, on passe à f en argument l'adresse &n de n. On doit donc déclarer que le paramètre correspondant de f est un pointeur


void f(int *p) { ... *p ...}

et appeler f(&n). Le passage de l'argument &n se fait par valeur, ce qui fait que l'adresse $a$ de n est copiée dans p. Dans le corps de la fonction, à chaque fois que *p est lue ou écrite, c'est donc la variable d'adresse $a$ qui est lue ou écrite, c'est-à-dire n. Ce mode de passage des arguments est appelé passage par adresse. On pourrait ainsi réécrire la fonction swap, donnée au § 32 :


void swap(int *x, int *y) {
  int temp = *x;

  *x = *y;
  *y = temp;
}

int main() {
  int a=1, b=2;

  swap(&a, &b);
  cout << a << "\t" << b << endl;
  return 0;
}

C'est la seule façon pour simuler le passage par référence en C. Le passage par adresse de C est plus lourd que le passage par référence de C++, mais il a au moins l'avantage d'être explicite du côté de l'appel : quand on voit un appel swap(&a, &b), on sait qu'il s'agit d'un appel par adresse, tandis qu'en C++, devant un appel swap(a, b), il faut aller voir la déclaration de la fonction swap pour savoir s'il s'agit d'un appel par référence ou d'un appel par valeur.

Il y a une autre différence importante entre l'appel par référence de C++ et l'appel par adresse de C. Dans l'exemple ci-dessous, le corps de la fonction peut modifier la valeur du paramètre p, en lui affectant une autre valeur que $a$, de sorte que p pointe alors vers une autre variable que n, ce qui n'a pas l'effet attendu (ni sur n ni sur l'autre variable en question):


int m = 2, n = 3;

void f(int *p) {
  p = &m;
  *p = 4;                 // ÉTRANGE : f modifie la valeur de m
}

L'appel f(&n) modifie toujours la valeur de la variable m et non celle de la variable n. Pour éviter ce risque, on devrait déclarer


void f(int *const p);

ce qui permettrait au compilateur de détecter une affectation p = .... On peut lire int *const p comme int* (const p), ce qui exprime que p est un pointeur constant. Ce comportement ne peut pas se produire avec le passage par référence de C++.

D'autre part, si un passage par adresse est souhaité seulement pour des raisons d'efficacité, pour éviter la copie d'un objet de grande taille, de façon analogue au passage par référence de C++, on doit déclarer également la fonction avec un const, mais dans une autre position :


void f(const int *p);

Ici, const int *p se lit comme (const int) *p qui exprime que *p est un entier constant. On peut même combiner les deux const pour spécifier que ni p ni *p ne peuvent être modifiés :


void f(const int *const p);


next up previous contents index
suivant: Pointeurs et tableaux monter: main précédent: Pointeurs   Table des matières   Index
R Lalement