next up previous contents index
Next: Pointeurs et tableaux Up: No Title Previous: Pointeurs

L'opération & et le passage par référence

    C, à la différence de Pascal, dispose d'une opération qui permet de calculer l'adresse d'un objet : pour toute expression E qui peut être placée à gauche d'une affectation (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 de type pointeur de t. Notons la simplification suivante : &*p en p, si p est d'un type pointeur.

Cette opération est essentielle pour simuler le passage des arguments par référence et utile dans quelques autres cas. Tout abus est nocif.

   

Les opérations * et & permettent d'expliquer le fonctionnement de l'appel par référence en C.

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

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

et appeler f(&n). Comme pour tout autre type, 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.

Par contre, si le corps de la fonction modifiait p, en lui affectant une autre valeur que a, p pointerait alors vers une autre variable que n, ce qui n'aurait 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;
}

L'appel f(&n) modifie 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 la constante p est un pointeur.

 

D'autre part, si un passage par référence est souhaité seulement pour des raisons d'efficacité, pour éviter la copie d'un objet de grande taille (ce sera le cas notamment pour certains gros struct), 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. Il est rare dans ce dernier cas que l'on souhaite que p soit modifié par la fonction ; on a donc tout intérêt à déclarer

void f(const int *const p);

pour faire vérifier par le compilateur que ni p ni *p ne sont modifiés.


next up previous contents index
Next: Pointeurs et tableaux Up: No Title Previous: Pointeurs
Jean-Philippe Chancelier
9/29/1998