informatique (MP2I/MPI) · Chapitre 8 · Exercice de TD
L'adresse d'une locale, et ce qu'elle vaut trois lignes plus loin
Mémoire, fichiers et entrées-sorties · niveau 2
Énoncé
Ce programme compile. Qu'affiche-t-il ?
int* faute(void) {
int x = 42;
return &x;
}
void bruit(void) {
int a = 111, b = 222, c = 333;
printf("(bruit : %d %d %d)\n", a, b, c);
}
int main(void) {
int* p = faute();
printf("juste apres : %d\n", *p);
bruit();
printf("apres bruit : %d\n", *p);
return 0;
}Corrigé
Sortie mesurée :
juste apres : 42
(bruit : 111 222 333)
apres bruit : 1
Et c'est là tout le danger : la première ligne est juste. Le bloc d'activation de faute a été dépilé, mais la case n'a pas été effacée — rien ne l'efface — et personne n'est encore venu s'y installer. Le programme paraît donc fonctionner.
L'appel à bruit réutilise cette même région de pile pour ses propres locales. Après lui, *p vaut , une valeur qui ne vient de nulle part. Un bogue de cette famille survit à toutes les exécutions de mise au point et se réveille le jour où l'on insère un appel de plus.
Le compilateur l'avait dit. gcc -Wall produit :
warning: address of stack memory associated with local variable 'x' returned
C'est un avertissement qu'on ne discute pas : il n'a pas de faux positif.
Les trois corrections possibles, par ordre de préférence :
- rendre la valeur et non l'adresse :
int faute(void) { return 42; }; - écrire dans une case fournie par l'appelant :
void f(int* sortie); - allouer sur le tas — et documenter que l'appelant devra libérer.
La troisième est la seule qui rende un pointeur, et c'est la seule qui déplace une charge sur l'appelant. On ne la choisit que si l'objet doit vraiment survivre.