Probleme – Le type somme d'OCaml contre l'union de C
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 6 — Types et structures de données abstraites
Énoncé
On veut représenter une valeur qui est soit un entier, soit une chaîne.
- Écrire les deux versions, et montrer par la mesure ce que l'union nue de C ne garantit pas.
- Que faut-il ajouter à l'union pour retrouver la sûreté ? Qu'est-ce qui reste non garanti ?
- Quel service OCaml rend-il que C ne rend pas, même avec l'étiquette ?
Corrigé
1. Les deux versions, et la mesure.
union brut { int i; char s[8]; }; /* les deux champs au MEME endroit */
on ecrit la chaine "abc", on relit l'entier : 6513249
on ecrit l'entier 1633837568, on relit la chaine : ""
sizeof(union brut) = 8
Le nombre n'est pas arbitraire : c'est , soit les octets 'a', 'b', 'c', ' 0' relus comme un entier de quatre octets en petit-boutiste. Une union ne retient pas ce qu'on y a mis : elle superpose deux lectures des mêmes octets, et le langage ne signale rien.
En OCaml, le type somme porte l'information dans la valeur :
type valeur = Entier of int | Chaine of string
let afficher = function
| Entier i -> Printf.printf "entier %d\n" i
| Chaine s -> Printf.printf "chaine %s\n" s
2. L'étiquette, et ce qui reste. On adjoint à l'union un champ qui dit quel membre est valide :
enum sorte { ENTIER, CHAINE };
struct valeur { enum sorte sorte; union { int i; char s[8]; } u; };
Mesuré : sizeof(struct valeur) vaut contre pour l'union nue — quatre octets pour l'étiquette. La lecture devient sûre si l'étiquette dit vrai. Or rien ne l'y oblige :
struct valeur menteur = { CHAINE, { .i = 42 } }; /* etiquette FAUSSE */
/* afficher(&menteur) imprime : chaine "*" -- 42 est le code de '*' */
L'étiquette est une convention, pas une garantie. Elle déplace la faute possible de la lecture vers l'écriture ; elle ne la supprime pas. Pour la supprimer, il faudrait rendre la structure opaque et n'offrir que deux constructeurs — c'est-à-dire refaire à la main ce qu'OCaml donne.
3. Les deux services d'OCaml.
- L'étiquette est posée par le constructeur, jamais par l'utilisateur : écrire
Chaine 42est un simple refus de typage. Le mensonge est impossible. - L'exhaustivité du filtrage est vérifiée. Si l'on ajoute un cas au type et qu'on oublie de traiter le nouveau, le compilateur le dit, et il dit lequel :
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched: Flottant _
Aucun switch de C ne donne cela : un default silencieux ou un cas oublié se compilent sans un mot.
La morale, qui vaut pour tout le chapitre. L'union de C et le type somme d'OCaml décrivent la même chose et occupent presque la même mémoire. Ce qui les sépare n'est pas la représentation, c'est ce que le compilateur promet de vérifier. Un type n'est pas une manière de ranger des octets : c'est un contrat, et sa valeur se mesure à ce qu'il refuse.
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.