Ours & Hippy — le blogOurs & Hippyourshippy@huoc.orgtag:blog.huoc.org,2009:atom2010-03-27T02:40:38+01:00tag:blog.huoc.org,2009:posts/37
Le code et ses raisons : typedef en C
2010-02-01T00:43:29+01:002010-03-27T02:40:38+01:00Nhat Minh Lê (rz0)
<p>Voici un vieil article qui traînait au fond de mes dossiers. Comme il
semble presque terminé, je me suis dit que j’allais le poster tout de
même…
</p><p>Aujourd’hui donc, au menu, <code>typedef</code>, le bien nommé, qui, très
naturellement, permet de définir un type, c’est-à-dire lui associer un
<em>nouveau</em> nom. En pratique, toutefois, les choses ne sont pas si
simples et il est bon de rappeler quelques règles d’utilisation
fondamentales avant d’attaquer la partie plus « philosophique » de
notre billet :
</p><ul><li><p><code>typedef</code> s’utilise comme une classe de stockage, dans une
déclaration classique ;
</p></li><li><p>à ce titre, un nom de type ainsi défini ne peut l’être qu’une seule
fois dans une unité de compilation (fichier C plus en-têtes
inclus) ;
</p></li><li><p>enfin, l’identificateur résultant de cette opération est un
<em>synonyme</em> du type utilisé : l’ancien et le nouveau type sont
utilisables de manière interchangeable.
</p></li></ul><h3><code>typedef</code>, pourquoi
</h3><p>Il y a maintes raisons pour lesquelles quelqu’un peut vouloir utiliser
<code>typedef</code>, mais il est possible de les ranger en deux familles :
</p><ul><li><p>celles qui visent à simplifier l’écriture : p.ex. en renommant un
pointeur de fonctions, ou en permettant d’omettre le mot-clé
<code>struct</code> ou <code>union</code> ;
</p></li><li><p>et celles qui introduisent une abstraction (p.ex. un type entier de
taille variable selon l’architecture, ou encore un type opaque).
</p></li></ul><p>Les deux usages ont leurs défenseurs et, dans une certaine mesure,
leurs détracteurs. Si le second emploi ne semble pas contestable en
tant que tel, c’est plutôt la nécessité de l’abstraction qui est
souvent remise en cause. Et si le premier apparaît comme une puissante
manifestation de <i>flemme</i>, l’histoire a montré que de grands noms le
soutiennent.
</p><p>On peut citer à cet effet Bjarne Stroustrup et son C++, dans lequel la
déclaration d’une variable de type structure ou union ne requiert pas
de mot-clé particulier.
</p><h3><code>typedef</code> abusif ?
</h3><p>Il y a une école, relativement répandue, notamment dans le milieu
scolaire, dont la position est de systématiquement créer un synonyme
pour toute structure ou union définie. Et, à l’inverse, il existe
certains cercles où cette pratique est violemment condamnée.<sup>α</sup> Elle
est entre autre explicitement déconseillée par le <a class="extern" href="http://www.openbsd.org/cgi-bin/man.cgi?query=style&sektion=9">KNF d'OpenBSD</a>.
</p><p>La raison invoquée est la suivante : utiliser <code>typedef</code> sur une
structure engendre une abstraction non toujours voulue. Lorsque
celle-ci est conçue comme un simple agrégat de données, et ses membres
pensés pour un accès direct, <code>typedef</code> masque la nature du type réel
à l’utilisateur.
</p><p>Dans l’autre camp, la parole est à l’uniformité. En effet, il existe
une multitude de niveaux, ou plutôt de formes, d’abstraction, au-delà
du simple qualificatif <i>opaque</i>, et son contraire. À partir de là, en
quoi est-il légitime de différencier (et à quel niveau cette
distinction doit-elle se faire) la structure directement accessible du
type abstrait ? Si un objet a une partie de ses membres accessibles,
doit-il utiliser le mot-clé <code>struct</code> ?
</p><div class="Notes"><p>α : Pour information, je suis également de ceux qui s’y opposent.
</p></div><h3>Zoom sur l’idée d’abstraction
</h3><p>Bien plus intéressant que les questions de raccourcis d’écriture, la
notion d’abstraction est celle qui domine réellement ce débat.
S’abstraire, mais de quoi ?
</p><p>On peut s’abstraire du matériel, tout d’abord, ou plus généralement de
la plateforme, du système : la définition de types (le plus souvent
entiers) susceptibles de changer d’une architecture à une autre est
une pratique commune, connue depuis longtemps sous Unix et en C, en
général. Les diverses normes en contiennent elles-mêmes une collection
importante : <code>size_t</code> en ANSI-C, <code>off_t</code> dans POSIX, ou encore les
fameux <code>intXX_t</code> du C99.
</p><p>Au niveau au-dessus, on peut vouloir s’abstraire de l’implémentation.
Par exemple, il existe de nombreuses façons d’écrire un dictionnaire,
mais l’interface basique est la même : on peut y chercher, insérer et
supprimer. Nommer son type <code>Dict</code> est une manière courtoise d’indiquer
ses intentions : on n’a besoin que de cette interface-ci.
</p><p>Typiquement, on se retrouve alors avec un type concret, disons <code>struct
htable</code>, qui modélise une table de hachage, que l’on souhaite masquer
derrière le type <code>Dict</code>. La bonne pratique du C veut que l’on passe en
paramètre de nos fonctions un pointeur vers <code>struct htable</code>. La
question qui divise est alors la suivante : faut-il utiliser <code>typedef</code>
la structure ou le pointeur vers celle-ci ?
</p><p>Ça a l’air idiot, n’est-ce pas ? Pourtant, il n’y a pas vraiment de
bonne réponse à cette question… et pour s’en convaincre, essayons
d’entrer dans le raisonnement des deux partis.
</p><p>La question fondamentale est : faut-il exposer la structure de
pointeur ? Si l’on redéfinit le type structure alors on garde la
structure de pointeur dans toutes nos interfaces ; si l’on redéfinit
le type pointeur, alors les interfaces ne montreront qu’un objet
complètement opaque.
</p><h4>Dans un camp
</h4><p>Un premier point de vue est de dire que l’objet se comporte comme un
pointeur (car c’est un pointeur !) donc l’analogie est justifiée. On
pourrait dire que cela restreint l’implémentation (on pourrait vouloir
utiliser une sorte d’ID quelconque). Mais étant donné que la majorité
des implémentations raisonnables, en C, utilisent une forme ou une
autre d’objet en mémoire dont on ne manipule qu’une référence (le
pointeur), la limitation est relativement virtuelle.<sup>β</sup>
</p><p>De plus, on peut aussi décréter que les types abstraits se manipulent
comme des types scalaires (entiers, pointeurs, etc.) et donc
finalement partagent plus ou moins toutes les propriétés de ceux-ci
(égalité, affectation, passage en argument, etc.). De ce point de vue,
les types obtenus en redéfinissant le type pointeur vers structure se
mêlent mieux aux types de base du C, tandis qu’en redéfinissant le
type structure lui-même, les opérations disponibles sur celui-ci sont
limitées ; en général, il n’est même pas permis de déclarer une
variable de ce type (seulement du type pointeur associé).
</p><div class="Notes"><p>β : Le seul domaine que je vois où elle pourrait devenir réellement
contraignante est la programmation système : le noyau conserve
souvent un certain nombre d’informations sous forme de tables
indexées, plutôt que de pointeurs vers des objets directement en
mémoire, ces objets n’étant pas forcément situés (entièrement) dans
l’espace mémoire de l’utilisateur.
</p><p>Cependant, il peut arriver qu’il soit plus commode de manipuler les
indices au lieu des pointeurs eux-mêmes, notamment si
l’identificateur entier est réutilisé pour indexer d’autres
structures.
</p></div><h4>Et dans l’autre
</h4><p>Cependant, si l’on opte pour le <code>typedef</code> simulant un type scalaire,
quelque part, on dissimule une information capitale : chaque objet de
ce type en cache un autre, « plus gros ». En d’autres termes, ce ne
sont que des références.
</p><p>À l’inverse, en conservant explicitement la syntaxe des pointeurs,
cette notion est clairement identifiée et passer un pointeur à une
fonction souligne le potentiel de celle-ci à modifier une donnée
tierce, masquée par le pointeur.
</p><p>À cela, les défenseurs de l’autre camp pourront rétorquer qu’une
structure de données complexe cache en général elle-même de multiples
niveaux d’indirections, et qu’ainsi il est tout aussi trompeur de
faire croire à l’utilisateur que c’est l’objet directement pointé qui
sera sujet à modification : bref, que l’on ne présente ainsi que le
haut de l’iceberg.
</p><p>Bien sûr, il est facile de considérer que la syntaxe n’est qu’un
indice conventionnel signalant au programmeur que de la mémoire
indirectement manipulée entre en jeu. Tout comme il est facile d’y
opposer le fait qu’à l’usage cela est invisible (le typage étant
réservé au prototype) et que la documentation a déjà à sa charge ce
genre de détails, ou encore que d’autres techniques sont plus
appropriées (p.ex. l’usage d’un qualificateur de fonctions <code>pure</code> tel
qu’on le trouve dans GCC). On pourrait contre-attaquer en remarquant
que le typage devrait alléger la documentation, à quoi on pourrait
répondre que celui du C est de toute façon trop faible pour
cela… J’aime débattre avec moi-même ; tout cela n’est qu’opinion, au
final. :)
</p><h3>Conclusion
</h3><p>Malgré toutes ces années à coder en C, je ne suis pas parvenu à forger
un avis prononcé sur la question. J’oscille moi-même entre les
diverses opinions, à mesure que de nouveaux argumentaires en faveur
d’une pratique ou d’une autre me parviennent…
</p><p>Ma position actuelle, si elle intéresse quelqu’un, relève d’un point
de vue que je qualifierais de « minimaliste » : je me suis fixé, pour
mes nouveaux programmes, de n’utiliser <code>typedef</code> que lorsque j’y vois
un intérêt immédiat (p.ex. pour la portabilité des types entiers). Le
raisonnement derrière cette politique est que l’abstraction est, en
C et au niveau où je travaille, relativement pauvre, et qu’un
changement d’implémentation ne saurait généralement se passer d’une
altération de l’API tout entière. Abstraire seulement le typage ne
suffit pas. Abstraire davantage entraîne un risque de complexité
accrue et de performances moindres.
</p><p>Mais je ne suis pas pour autant fondamentalement opposé à l’usage de
<code>typedef</code> dans d’autres scénarios. Une bibliothèque de très haut
niveau qui reposerait sur la manipulation d’objets (au sens POO)
pourrait choisir de nommer ses classes avec <code>typedef</code>. Une telle
convention aurait du sens.
</p><p>Je concluerai donc sur ces mots : il faut ainsi prendre en compte le
contexte et établir des conventions. Pour rabâcher le principe de
style fondateur et bien connu : après le bon sens, la cohérence doit
primer.
</p>