Probleme – Trois niveaux de défense, et ce que chacun coûte
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 4 — Discipline de programmation, validation et test
Énoncé
On dépile une pile vide. Trois réalisations sont possibles.
- Écrire les trois : aucune vérification, assertion, code de retour. Mesurer ce que chacune fait.
- Laquelle choisir, et selon quel critère ?
- Que fait OCaml à la place, et quelle garantie cela apporte-t-il ?
Corrigé
1. Les trois réalisations.
typedef struct { int t[CAP]; int n; } pile; /* INVARIANT : 0 <= n <= CAP */
/* (a) aucune defense */
int depiler_a(pile* p) { p->n = p->n - 1; return p->t[p->n]; }
/* (b) assertion : la precondition « pile non vide » est du ressort de
l'appelant, et sa violation est une faute de programmation. */
int depiler_b(pile* p) {
assert(p != NULL && p->n > 0);
p->n = p->n - 1;
return p->t[p->n];
}
/* (c) code de retour : l'echec est une issue PREVUE, visible dans la
signature. Renvoie true et ecrit *sortie en cas de succes. */
bool depiler_c(pile* p, int* sortie) {
if (p == NULL || p->n == 0) { return false; }
p->n = p->n - 1;
*sortie = p->t[p->n];
return true;
}
Mesuré, sur une pile vide :
(a) rend une valeur quelconque -- ici -1 -- et laisse p->n a -1
(b) Assertion failed: (p != NULL && p->n > 0), function depiler_b,
file pile.c, line 9. code de sortie 134
(c) rend false, et p->n vaut TOUJOURS 0
Le vrai dégât de (a) n'est pas la valeur rendue, c'est la ligne suivante : p->n vaut désormais , l'invariant de la structure est rompu, et le prochain empiler écrira dans p->t[-1], hors de la structure. Une faute unique s'est transformée en corruption durable, à mille instructions de sa cause. C'est exactement ce que le chapitre appelle « une précondition violée produit un résultat faux qui se propage ».
2. Le critère de choix est celui de l'exercice 4.3 : qui peut être fautif ?
- Si dépiler une pile vide ne peut arriver que par une faute de programmation — l'appelant a testé
est_videjuste avant, ou compte les éléments —, c'est (b). L'assertion documente le contrat et l'éprouve pendant le développement. - Si le vide est une issue normale du programme — on dépile jusqu'à épuisement, la pile vient d'une entrée —, c'est (c). Le contrat s'élargit : la fonction accepte la pile vide et rend une réponse définie.
(a) n'est jamais le bon choix, et le mesurer est le seul moyen de s'en convaincre : il ne coûte rien à écrire et fait perdre des heures à déboguer.
Noter que (b) et (c) n'ont pas la même signature : (c) doit rendre un booléen et écrire le résultat par pointeur, parce qu'aucune valeur de int n'est disponible pour signaler l'échec. C'est le problème que le type option résout au chapitre chap:langage-ocaml.
3. Ce que fait OCaml.
let p = Stack.create () in
try ignore (Stack.pop p) with Stack.Empty -> print_string "pile vide\n"
Stack.pop sur pile vide : exception Stack.Empty
t.(5) sur un tableau de 3 cases : Invalid_argument "index out of bounds"
Le module Stack lève Empty, et l'accès hors bornes lève Invalid_argument. La différence avec (a) est totale : il n'existe aucune façon d'obtenir silencieusement une valeur invalide. La différence avec (b) est plus subtile — une exception se rattrape, une assertion non — et c'est celle qui compte : l'exception laisse à l'appelant le choix de traiter ou de laisser passer, là où l'assertion décide pour lui.
| Détecte ? | Rattrapable ? | Survit à la livraison ? | |
|---|---|---|---|
| (a) rien | non | --- | --- |
| (b) `assert` | oui | non | non (`-DNDEBUG`) |
| (c) code de retour | oui | oui | oui |
| OCaml, exception | oui | oui | oui |
OCaml garantit qu'on ne lira pas de mémoire invalide. Il ne garantit pas qu'on a pensé au cas de la pile vide : un programme qui appelle Stack.pop sans try s'arrête sur une exception non rattrapée. Le langage a transformé une corruption silencieuse en arrêt bruyant, ce qui est un immense progrès — mais la réflexion sur les cas limites reste entièrement à la charge du programmeur. C'est la formule du chapitre : « en OCaml, une grande part est déléguée au compilateur — mais rien ne protège de l'arithmétique, ni d'une logique fausse ».
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.