[Atom] [Mail] [Twitter]
Liens : git · hacks · divers · cabale · buzz! · à propos +
Au menu
\/

Écrire du code C avec Emacs

#. Par rz0. Mis à jour le 20.02 2010 à 10:02. Aucun commentaire.
c débutants emacs pratique programmation tutorial

Et de deux ! —rz0

Cet article d’introduction s’intéresse aux aspects basiques liés à l’écriture et à la gestion du code C basique avec Emacs, et, bien sûr, aux fonctionnalités qui vous y aideront.

Édition du code avec le c-mode

Le mode d’édition du code C dans Emacs se nomme très simplement le c-mode et est inclus par défaut avec l’éditeur ; vous pouvez aussi obtenir la dernière version du mode (qui est développé en dehors d’Emacs lui-même) sur le site du cc-mode. Le c-mode offre plusieurs fonctionnalités et commandes intéressantes.

Indentation automatique

Tout d’abord, comme tous les modes de programmation dans Emacs, le c-mode possède l’indentation automatique. Cela signifie que la touche TAB (ou, de manière équivalente, C-i) n’est pas utilisée pour insérer un caractère « tabulation » mais pour indenter la ligne actuelle, c’est-à-dire aligner le début de celle-ci sur la colonne adéquate, comme vous le feriez vous-même si Emacs n’était pas là. Il est important de remarquer que cette opération ne tient absolument pas compte de l’indentation actuelle de la ligne : qu’elle soit trop décalée à gauche ou à droite, Emacs la replacera au bon endroit.

Emacs indentera la ligne selon deux informations : le contexte syntaxique (si vous êtes au début d’une instruction, au milieu d’un appel de fonction, etc.) et le style sélectionné (par exemple, K&R ou BSD ; voyez mon introduction à la configuration d’Emacs pour plus d’informations).

Comme il peut devenir lassant de devoir appuyer (quoique ce ne soit qu’une seule fois) sur la touche TAB à chaque début de ligne, sachez que vous pouvez aller à la ligne et indenter en une seule commande : C-j (newline-and-indent).

Mais l’indentation d’Emacs ne se limite pas à la ligne actuelle ! Vous pouvez également lui demander d’indenter tout un bloc ou une fonction pour vous. Il s’agit respectivement des commandes C-M-\ (indent-region) et C-c C-q (c-indent-defun), qui indentent la région actuelle (la zone de texte située entre la marque, usuellement apposée par C-SPC, et le curseur) et la fonction dans laquelle se trouve le curseur. En théorie, vous pourriez indenter tout le fichier ainsi, mais je vous le déconseille ; lalgorithme est lent et perd en précision si les constructions sont trop vilaines ou complexes. En revanche, si vous effectuez une modification au milieu d'un bloc, et que, par exemple, vous supprimez un niveau d'indentation, ces commandes peuvent s'avérer très utiles. Parfois, en revanche, vous voulez aligner des éléments (comme le ferait la toucheTABen temps normal) sans qu'Emacs n'essaie d'indenter la ligne. Pour cela, vous pouvez utiliser la commandeM-i(tab-to-tab-stop). C'est le cas, par exemple, si vous alignez vos déclarations ou vos instructions dans unswitch. Pour résumer, voici un tableau des commandes d'indentation et d'alignement : |TAB | indente la ligne actuelle | |C-j | retour à la ligne et indentation simultanés | |C-M-‘ | indente la région actuelle

C-c C-q indente la fonction actuelle
M-i aligne le curseur sur la prochaine colonne de tabulation

Gestion des commentaires

De même qu’Emacs sait indenter le code automatiquement, il sait insérer des commentaires au bon endroit ; pour se faire, il vous suffit d’actionner la commande M-; (comment-dwim). Que vous soyez sur une ligne vide ou non, Emacs déterminera la syntaxe à adopter. C’est la commande idéale pour ceux qui trouvent les commentaires de type C (/* ... */) trop longs à taper.

De plus, Emacs dispose des commandes de traitement de régions suivantes : comment-region et uncomment-region. Elles sont accessibles par M-x et qui peuvent être associées à une combinaison de votre choix si vous en sentez le besoin. Je vous laisse deviner ce qu’elles font. Elles peuvent être utiles à ceux qui aiment isoler des morceaux de code avec des commentaires (personnellement, je préfère utiliser #if 0).

M-; insère un commentaire

Déplacements contextuels

Enfin, le c-mode dispose de mécanismes de navigation contextuelle. La plupart des commandes utilisées sont communes à tous les modes d’édition mais leur fonctionnement a été adapté à la structure du code C.

Vous pouvez, comme toujours, vous déplacer de mot en mot avec les habituels M-b (backward-word) et M-f (forward-word).

Juste au niveau supérieur, les commandes C-M-b (backward-sexp) et C-M-f (forward-sexp) vous permettent de sauter d’un bout à l’autre d’expressions équilibrées (un groupe entre parenthèses, crochets ou accolades). En complément, C-M-u (backward-up-list) et C-M-d (down-list) naviguent à travers les niveaux de parenthésage : C-M-u vous amène au début du groupe dans lequel se situe le curseur et C-M-d vous transporte au début du prochain groupe à l’intérieur de l’expression actuelle. Le plus simple est encore de les essayer pour en voir les effets !

Vous pouvez également vous déplacer au début et à la fin des instructions avec M-a (c-beginning-of-statement) et M-e (c-end-of-statement), respectivement.

Enfin, les commandes beginning-of-defun et end-of-defun font, sans surprise, le voyage au début et à la fin d’une définition de fonction.

Récapitulons les commandes directement accessibles :

C-b, C-f déplacement au mot
C-M-b, C-M-f déplacement à l’expression (équilibrée)
C-M-u, C-M-d déplacement à l’expression (équilibrée)
M-a, M-e déplacement à l’instruction

Plusieurs fichiers sources

Avec ce que nous venons juste de voir, nous sommes maintenant capables d’éditer un fichier source C. Toutefois, la plupart des projets ne sont pas constitués d’un seul fichier, aussi, nous allons nous pencher sur la gestion de plusieurs fichiers dans Emacs. Cette section n’est pas directement liée à la programmation en C, mais la question revient fréquemment chez les novices, et c’est pourquoi j’ai décidé de la traiter ici.

Hiérarchies de fichiers

Typiquement, le scénario est le suivant : vous possédez plusieurs fichiers sources regroupés dans un dossier. Quelque chose que beaucoup de gens aiment faire est pouvoir regarder la hiérarchie des fichiers et naviguer de fichier en fichier. Pour cela, Emacs offre deux mécanismes : dired, un explorateur de fichiers, et la speedbar, une barre latérale.

Selon vos goûts, vous préférerez sans doute l’un ou l’autre. Les différences sont multiples : dired possède plus de facilités pour la gestion des hiérarchies de fichiers et de dossiers, avec toutes les fonctions classiques que l’on pourrait attendre d’un explorateur de fichiers, et se présente comme une application Emacs à part entière, utilisant ses propres buffers. À l’opposé, la speedbar s’affiche dans un cadre (frame) à part, et son contenu est constamment recalculé de sorte que le dossier affiché dans la barre soit toujours celui du fichier actuel.

Si vous n’êtes pas habitué à la terminologie emacsienne, sachez qu’une fenêtre correspond à une division de l’affichage d’Emacs (ce que vous obtenez avec C-x 2 par exemple) tandis qu’un cadre est un ensemble de fenêtres indépendant, ce qui, en mode graphique, correspond, pour la plupart des gestionnaires de fenêtres, à ce qu’ils appellent une fenêtre. Sauf mention contraire, nous utiliserons ici le vocabulaire d’Emacs.

À l’usage, l’un ou l’autre est assez naturel. Pour lancer dired, utilisez la commande C-x d (dired) ou C-x C-f (find-file) avec le chemin d’un dossier. Pour lancer la speedbar, tapez M-x speedbar ; un cadre devrait apparaître à côté de votre cadre principal.

Une fois à l’intérieur de l’un ou l’autre, placez-vous sur un fichier et tapez RET (dired-advertised-find-file ou speedbar-edit-line) pour le visiter. Sous dired, vous avez l’option de visiter le fichier dans une autre fenêtre avec o (dired-find-file-other-window).

Si vous voulez parcourir des sous-dossiers, sachez que dired prône une disposition plane tandis que la speedbar affiche les dossiers et les fichiers sous forme arborescente. Lorsque votre curseur est sur une ligne correspondant à un sous-dossier, tapez i sous dired ou + dans la speedbar (respectivement, dired-maybe-insert-subdir et speedbar-expand-line) pour insérer son listage dans le buffer actuel. Pour supprimer l’affichage d’un sous-dossier, placez-vous sur son en-tête (son chemin, juste au-dessus du listage, pour dired, ou son nom dans l’arborescence, pour la speedbar) et exécutez la commande M-x dired-kill-subdir ou - (speedbar-contract-line), pour dired et la speedbar respectivement. Au lieu de l’effacer complètement, dired vous propose aussi d’utiliser la commande $ (dired-hide-subdir), qui, malgré son nom, permet de cacher et de découvrir le contenu du dossier.

Si votre gestionnaire de fenêtres ne vous permet pas de déplacer facilement le focus au clavier, sachez qu’Emacs vous permet d’aller de cadre en cadre avec C-x 5 o (other-frame).

Et comme précédemment, un petit récapitulatif :

  dired speedbar
parcourir un dossier C-x d automatique
visiter un fichier RET RET
insérer un sous-dossier i +
cacher un sous-dossier $ -

Plusieurs fichiers dans Emacs

Maintenant que vous savez accéder facilement à plusieurs fichiers de votre projet, vous risquez fort d’être vite submergé par le grand nombre de buffers ouverts. Emacs ne dispose pas de tabs (ce n’est pas comme si vous alliez vous servir de votre souris pour y accéder, de toute manière). En revanche, plusieurs modes sont disponibles pour gérer vos buffers. Je ne vais pas tous les passer en revue ici, je ne donnerai qu’une seule solution.

La première commande à connaître pour bien gérer vos multiples buffers est sans doute C-x C-b (list-buffers). Elle démarre le gestionnaire de buffers. Ce dernier se présente comme une simple liste des buffers ouverts. Vous pouvez y effectuer les opérations de navigation habituelles, dont la recherche (avec C-s (isearch-forward) pour les étourdis). RET (Buffer-menu-this-window) vous permet de visiter le buffer à la ligne actuelle. Une autre utilisation très pratique est de supprimer plusieurs buffers à la fois. Pour cela, marquez chaque buffer à détruire avec la commande d (Buffer-menu-delete). La lettre D apparaît alors à gauche du nom. La commande x (Buffer-menu-execute) effectue l’opération de nettoyage en elle-même. Ainsi, si vous vous êtes trompé dans votre marquage, vous pouvez revenir en arrière en utilisant la commande u (Buffer-menu-unmark) sur la même ligne.

Tout cela est bien utile pour faire le ménage mais ne vous aide guère pour votre besoin le plus simple : naviguer de buffer en buffer. Pour cela, il y a bien sûr la commande C-x b (switch-to-buffer) par défaut d’Emacs pour changer de buffer. Mais elle n’est pas très pratique ; en effet, elle demande d’entrer le début du nom pour accéder au buffer. Fort heureusement, il existe (au moins) un mode qui vous permet de changer de buffer en tapant quelques lettres n’importe où dans le nom. Je vous présente là l’iswitchb-mode. Pour l’essayer, il vous suffit d’effectuer la commande M-x iswitchb-mode ; de même pour le désactiver. Pour l’adopter définitivement, ajoutez à votre .emacs la ligne :

(iswitchb-mode t)

La combinaison C-x b invoquera à présent iswitchb-buffer. Si vous l’essayez, vous remarquerez que la liste des buffers qui sont encore candidats à la sélection est maintenant affichée à côté de votre saisie. Dans ce mode, les commandes C-r (iswitchb-prev-match) et C-s (iswitchb-next-match) vous permettent de faire défiler les éléments de la liste de manière cyclique, et RET sélectionne le premier buffer de la liste. Ainsi, vous n’aurez plus à taper le début exact du nom que vous voulez, il vous suffira simplement d’entrer quelques lettres remarquables et, moyennant quelques rotations de la liste, vous arriverez rapidement à destination.

Vous pouvez aussi demander à utiliser des expressions régulières. La commande C-t (iswitchb-toggle-regexp), dans l’iswitchb-mode, vous permet cela. Par exemple, pour obtenir une liste des fichiers C ouverts, vous pourriez utiliser le motif \.c$. Veuillez cependant prendre note du fait que les expressions régulières d’Emacs diffèrent d’autres syntaxes, soyez donc averti et pensez à lire le manuel (avec C-h i (info), par exemple) si vous désirez en faire bon usage.

Avec l’habitude, ce système très flexible remplace avantageusement les tabs. Vous n’êtes plus lié à une liste prédéfinie ; vous avez la possibilité de fabriquer rapidement la vôtre, adaptée à votre requête. Du reste, il vous permettra de même d’aller de buffer en buffer. Et tout cela pour un encombrement nul dans vos autres actions !

Et pour les petits étourdis :

C-x C-b gestionnaire de buffers
C-x b change de buffer
C-r et C-s défilement de la liste (dans iswitchb)
C-t activation des regexps (dans iswitchb)

Édition simultanée de plusieurs fichiers

Bien sûr, vous n’êtes pas encore capable de vous dédoubler pour éditer plusieurs fichiers à la fois, mais parfois, vous pourriez vouloir afficher deux fichiers (voire plus) côte à côte.

Pour se faire, rien de plus simple. Il vous suffit de partager votre affichage en deux : verticalement avec C-x 2 (split-window-vertically) ou horizontalement avec C-x 3 (split-window-horizontally). Pour passer d’une fenêtre à l’autre, utilisez C-x o (other-window). Enfin, C-x 0 (delete-window) et C-x 1 (delete-other-windows) vous permettent respectivement de supprimer la fenêtre sur laquelle se trouve le focus et de ne laisser qu’elle.

Ainsi, vous pourrez plus facilement comparer ou transférer du code d’un fichier à un autre.

Sachez également qu’il existe des commandes qui marient une action donnée à un changement de fenêtre, telles queC-x 4 f (find-file-other-window) et C-x 4 b (switch-to-buffer-other-window). La première ouvre un fichier, à l’instar de C-x C-f (find-file) mais l’affiche dans une fenêtre séparée. La seconde agit comme C-x b (switch-to-buffer) mais, de même, place le contenu dans une autre fenêtre. Si le cadre n’est constitué que d’une fenêtre, celui-ci est automatiquement scindé en deux.

C-x 2, C-x 3 création de fenêtres
C-x o déplacement de fenêtre à fenêtre
C-x 0, C-x 1 suppression de fenêtres
C-x 4 f visite un fichier dans une autre fenêtre
C-x 4 b affiche un buffer dans une autre fenêtre

Gestion de l’historique des modifications avec VC

Le dernier outil essentiel à tout projet sérieux est un gestionnaire de versions. Si vous n’êtes pas familier avec ce concept, voici une brève introduction. Vos fichiers seront amenés à grandement évoluer avec les phases d’essai, de correction, d’essais publics, d’amélioration, etc. Il est donc primordial de contrôler ces changements sous forme de versions. Des logiciels spécialisés appelés « systèmes de contrôle de versions » (VCS, Version Control Systems en anglais) permettent de faire cela. Nous n’entrerons pas ici dans le détail du fonctionnement de ceux-ci, mais le principe général est le suivant : chaque fichier est associé à une copie persistante, qui représente tous ses états depuis le début, toutes ses versions, sous forme de différences. Cela signifie qu’une version n’est pas stockée en entier mais uniquement sous une forme relative à une version voisine. Dans sa réalisation la plus classique, par exemple, si vous ajoutez une ligne à un fichier, le fichier de contrôle, contenant la copie persistante, enregistrera une différence d’une ligne par rapport à la version précédente.

Emacs intègre un support de certains de ces systèmes, dont RCS, CVS, SVN et Git. Pour illustrer cette intégration, qui porte le petit nom de VC pour, vous l’aurez deviné, Version Control, nous allons nous intéresser au plus simple d’entre eux : RCS. Mais les commandes demeurent approximativement les mêmes quel que soit le programme de gestion de versions.

Traditionnellement, les fichiers de contrôle de RCS sont stockés localement dans un sous-dossier de celui où se situent vos fichiers sources, ce sous-dossier portant le nom RCS. Si vous ne le créez pas par vous-même, Emacs vous le proposera en temps voulu.

L’utilisation de VC repose sur la commande C-x v v (vc-next-action). Comme son nom l’indique, elle fait… des choses, selon l’état actuel du fichier. Avec RCS, un fichier peut avoir plusieurs états. Dans son utilisation classique, ce sont les suivants.

  1. Au départ, le fichier est inconnu de RCS. Lorsque vous l’enregistrez auprès de ce dernier (avec C-x v v), celui-ci s’approprie le contenu. Le fichier et la version la plus récente connue de RCS sont alors les mêmes.

  2. À ce stade, vous ne pouvez pas modifier le fichier à nouveau. Il vous faut prévenir RCS de votre intention, ceci afin d’empêcher que plusieurs personnes ne modifient le même fichier en même temps. Cette action s’appelle le verrouillage et s’effectue aussi par C-x v v. Une fois le fichier verrouillé, vous êtes sûr qu’il est la seule copie de travail existante.

  3. Une fois vos modifications effectuées, C-x v v synchronise votre brouillon avec la copie de contrôle. VC vous demande alors de fournir un commentaire décrivant les changements, commentaire qui sera inscrit dans une espèce de journal de bord du fichier. La fenêtre d’édition du message est tout à fait banale et la validation est obtenue par le traditionnel C-c C-c, valable pour la plupart des formulaires sous Emacs. Après cette étape, le fichier et la version la plus récente connue de RCS sont à nouveau les mêmes.

Tout ceci est bien joli, mais à quoi serviraient un historique et un journal que l’on ne peut consulter ?

La commande C-x v ~ (vc-version-other-window) vous permet d’accéder au contenu d’une version antérieure de votre fichier. Le numéro de la version vous sera demandé. Mais que faire si vous ne vous rappelez plus de la bonne version ? C’est à cela que servent les commentaires que vous avez pris soin d’entrer tout au long de votre travail ! Pour voir la liste des versions ainsi que les messages associés, tapez C-x v l (vc-print-log).

Une fois la version voulue récupérée dans un buffer secondaire, vous pouvez afficher les deux buffers en parallèle comme nous avons vu précédemment. Mais VC propose une commande intéressante pour vous aider à voir les différences entre vos versions : C-x v = (vc-diff). Elle vous propose une comparaison rapide de deux versions, avec la différence placée dans un buffer à part.

Cela peut sembler beaucoup de choses à enregistrer si vous n’avez jamais utilisé de VCS auparavant, mais j’ai moi-même débuté dans ce domaine avec VC qui est, à mon avis, un moyen simple de se familiariser avec le concept tout en restant bien au chaud dans son éditeur préféré. Et pour vous y aider, voici un… récapitulatif ! Avouez que vous ne vous y attendiez pas.

C-x v v change d’état
C-x v ~ ressuscite une version
C-x v l invoque la liste des versions
C-x v = compare deux versions

Cette présentation de VC et de RCS achève ce premier article. Dans le prochain chapitre (peut-être…α), nous parlerons de la compilation de fichiers et de projets écrits en C, à l’aide de makefiles, le tout depuis Emacs. Contrairement à ce premier tutoriel, il sera bien moins porté sur Emacs lui-même que sur les outils à mettre en œuvre pour compiler depuis Emacs, c’est-à-dire GCC et make.

α : L’histoire nous a montré que ce « peut-être » a su se transformer en « non ». :)

Je remercie Metzgermeister et nicofrand pour la relecture de cet article.

[ tag:blog.huoc.org,2009:posts/40 ]
Aucun commentaire · Commenter

/\ \/

Conventions : quelques vers à la gloire du Grand

#. Par rz0. Publié le 16.08 2009 à 14:54. 8 commentaires.
c idiomes poésie pratique programmation sectarisme

J’étais parti pour écrire une réponse au billet de GuilOooo sur BHM traitant des commentaires, mais la raison m’a rappelé à elle et j’ai décidé de vous offrir ce (très) court poème à la place :

Car pour la descendance, Dieu l’ordre dicta :
Pour les itérations, les compteurs i, j, k ;
n, m : des quantités ;
p, q devront pointer ;
c pour les caractères ;
s et t pour les chaînes.
Mais ni o ni l, les suppôts du binaire,
Pour que le programmeur toujours lise sans peine.

Et si vous avez apprécié cette haute poésie, je ne peux que vous conseiller cette belle lecture !

[ tag:blog.huoc.org,2009:posts/23 ]
Voir les commentaires · Commenter

/\ \/

Conventions : le retour

#. Par rz0 dans Le code et ses raisons. Publié le 25.08 2009 à 14:46. Aucun commentaire.
>. Le code et ses raisons : goto en C
c débutants idiomes pratique programmation

Comme il ne coûte pas cher de déterrer des brouillons presques prêts pour en faire des articles, voici le retour de l’article sur les conventions que j’avais commencé à écrire, et duquel avait été tiré mon petit poème.

Ceci est donc un article orienté débutants. « Woah, rz0 il fait des articles de vulgarisation maintenant. » Et ouais les cocos, ce sont des choses qui arrivent, dans la vie. Parfois, en se levant le matin, et qu’il est déjà midi, on n’a pas envie de plancher tout de suite sur la refactorisation qui a échoué la veille. Bref, trève de bavardage.

Les conventions, c’est quoi ? Le principe est simple, c’est celui de la bonne ptite ouvrière : faire la même chose toujours de la même manière. Oubliez vos pulsions d’artiste et embrassez pleinement la voie de la machine (l’autre voie de la machine, pour ceux qui sont déjà en prépa) !

Et je ne parle pas ici de ne pas nommer une variable prgcnt et une autre MyFckngVisitorCount juste à côté, ça c’est la différence entre lEs GenS Ki ÉkrIv KoM sA et le monde civilisé.

Non, l’idée est que si une variable assure un certain rôle plusieurs fois, alors elle devrait avoir le même nom à chaque fois. Un peu comme les païens du C++ appellent toujours (sont toujours forcés d’appeler) le pointeur sur l’objet manipulé this. Si vous écrivez une bibliothèque de tableaux dynamiques, vous pouvez par exemple décider que n correspond toujours au nombre d’éléments du tableau actuellement considéré.

Heureusement, Le Créateur a pensé à vous, et les anciens vous ont légué une pelleté de noms prêts à l’emploi. La bonne nouvelle, c’est que vous pouvez les utiliser. La mauvaise, c’est que vous devez les utiliser. (Et c’est ici qu’intervenaient mes talents poétiques.)

Car pour la descendance, Dieu l’ordre dicta :
Pour les itérations, les compteurs i, j, k ;
n, m : des quantités ;
p, q devront pointer ;
c pour les caractères ;
s et t pour les chaînes.
Mais ni o ni l, les suppôts du binaire,
Pour que le programmeur toujours lise sans peine.

Mais il est souvent tout aussi utile de définir vos propres conventions, pour vos objets, vos types, vos règles du jeu. Ou simplement de préciser les règles traditionnelles (comme nous l’avons vu juste au-dessus avec n).

Toujours utiliser les mêmes noms pour les variables de mêmes rôles vous évitera d’avoir à commenter leur déclaration avec des banalités du genre :

int lol;      /* Nombre de cailloux dans la marre. */

Remarquez aussi, souvent, vous pourrez choisir des noms conventionnels courts. Si après avoir lu mon chant à la gloire du Grand, vous écrivez toujours indiceCaillou au lieu d’i dans votre module de gestion des cailloux dans les marres, ma foi, je ne peux rien pour vous (sinon prier pour votre âme, pour lui éviter le knout).

Un point de débat possible sur la question, cependant, est le suivant : faut-il privilégier la précision ou la convention ? (Et non, indiceCaillou ne relève pas de la précision à mes yeux.)

Imaginez, vous utilisez p pour toutes vos variables de type Meuh, en entrée des fonctions de votre module. Cela tombe bien, elles ne prennent chacun qu’un seul objet à examiner ! Parfois, en interne, vous avez besoin d’un second pointeur, que vous appelez naturellement q ; tout va bien, vous avez spécialisé les conventions existantes, parfait. Maintenant, imaginez que vous vouliez ajouter une fonction copier qui copie la valeur d’un Meuh dans un autre. Elle aura deux paramètres. Doit-on les appeler dest et src, pour la précision ? Ou p et q pour suivre la convention ? Cela nous amène au point suivant.

Tout comme il est important de se fixer des conventions pour les noms de variables, il est souvent très utile de vous définir des ordres de passage d’arguments respectés par toutes les interfaces de votre unité. Ce sont des règles du type « l’objet ayant le rôle X précède toujours cela de rôle Y ».

L’exemple typique ici est la fonction de copie. La destination doit-elle précéder ou suivre la source ? Il n’y a pas de réponse universelle, et chacun a ses préférences, mais il est important de toujours utiliser la même stratégie de classement de vos paramètres.

Pour revenir au problème précédent, s’il semble d’abord aberrant de vouloir appeler ses paramètres p et q, si l’on considère que l’ordre de passage est bien établi, alors l’usage ne fait aucun doute : p est toujours le premier pointeur et q le second, l’ordre des arguments se chargeant de donner son sens à cette position. Les deux alternatives sont donc viables ; à vous de choisir celle qui colle le plus à votre style.

Notez enfin que le point de vue est souvent influencé (un peu ou beaucoup) par le paradigme dominant. Si, disons, vous réfléchissez en orienté objet et avez codifié que le paramètre numéro un était l’objet sur lequel on opérait, alors le fait de toujours nommer cet objet de la même façon prend tout son sens. Si au contraire, vous adoptez une vision plus procédurale, un autre schéma de noms vous sera peut-être plus naturel…

Et voilà pour les conventions ! Notez que j’ai fait exprès d’éluder le sujet des idiomes, sujet fort proche. La raison ? La flemme, bien sûr. Mais pourquoi pas un jour prochain, ou un autre encore. Qui sait, tant qu’il y a du désir, et des gens pour me lire. Lalala.

[ tag:blog.huoc.org,2009:posts/25 ]
Aucun commentaire · Commenter

/\ \/

Le code et ses raisons : goto en C

#. Par rz0 dans Le code et ses raisons. Publié le 10.09 2009 à 13:06. 14 commentaires.
<. Conventions : le retour
>. Le code et ses raisons : typedef en C
c débutants goto idiomes pratique programmation

Tak tak, aujourd’hui Ours répond ! Quelqu’un m’a demandé hier pourquoi mon code contenait autant d’instructions goto, la malfamée. Comme c’est une question qui revient souvent, je me suis dit que cela pouvait intéresser notre audience moins expérimentée en C. Here comes!

Ahem, commençons. goto, c’est un truc un peu vite fait obscur du C, un peu comme les trucs Caml obscurs de bluestorm mais quand même pas au même niveau de louchitude. goto, ça déchaîne les jeunes comme les vieux, tout ça au final pour une pauvre instruction qui sautille. Bref, goto ça transporte le flot d’exécution d’un endroit à un autre d’une fonction. Ça, c’est la description objective, notre ptit Get the Facts avant d’embrailler sur la vraie question : pourquoi diable utilise-t-on goto ? Je ne pourrai pas répondre pour « on » mais je peux répondre pour moi !

goto est avant tout l’instrument du pauvre. Voilà qui est dit. La raison principale derrière l’utilisation de goto est que celui-ci permet d’émuler des flots d’exécution atypiques et indisponibles en C. On parle souvent de gestion des erreurs, d’exceptions du pauvre ; ce n’en est qu’un cas particulier. L’idée générale est celle que je viens d’énoncer : goto permet d’écrire des programmes C avec des structures non présentes directement dans le langage.

Lol le C, c’est pour les ptits joueurs, ya pas d’exceptions

Quelques exemples maintenant, avec tout d’abord le grand classique : la sortie de boucles imbriquées et la gestion des erreurs. Pourquoi ai-je donc regroupé ces deux cas qui apparaissent distincts ? Car ils modèlent tous deux un type de flot permis par les exceptions dans les langages qui en possèdent : on dispose de contextes imbriqués desquels on veut se défaire en un saut.α

α : Notez que je n’ai pas dit que les exceptions pouvaient être émulées par des goto, juste qu’il y a des ressemblances dans le flot obtenu.

Jetons un œil à deux bouts de code. La sortie de bloc, tout d’abord :

{
    while (a) {
        while (b) {
            if (c)
                goto end;
            ...
        }
    }
end:
    ...
}

Et la gestion des erreurs, dans sa forme modelant une pile :

{
    if ((a = f(...)) == NULL)
        goto bada;
    if ((b = g(...)) == NULL)
        goto badb;
    ...
    return ...;
badb:
    antif(a);
bada:
    return ...;
}

Le premier exemple ne devrait demander aucun commentaire. Le second n’est pas aussi évident : il n’exhibe pas clairement l’idée de contextes imbriqués. Celle-ci est en vérité masquée par le court-circuitage introduit par les sauts. Nous aurions pu écrire :

{
    if ((a = f(...)) != NULL) {
        if ((b = g(...)) != NULL) {
            ...
        } else {
            antif(a);
            goto bada;
        }
    } else {
    bada:
        return ...;
    }
    return ...;
}

OK, le tout s’est sensiblement enlaidi, et je vous rassure, personne ne l’écrit ainsi (car en plus d’être laid, on effectue beaucoup plus de sauts en cas d’erreur). L’avantage de cette forme, cependant, est qu’elle modélise beaucoup plus précisément le comportement d’une exception dans un langage de plus haut niveau : chaque bloc if correspondrait à un bloc try (ou équivalent) avec une clause de finalisation (contenue dans la partie else). Lorsqu’une condition exceptionnelle se produit, les clauses de finalisation sont déroulées les unes après les autres, dans l’ordre d’empilement des contextes (représentés ici par les blocs).

À part remarquer que la gestion des erreurs, cas normal d’utilisation des exceptions, n’est pas celui qui possède la correspondance la plus naturelle avec l’utilisation de goto, nous n’avons plus grand chose à dire ici. Passons donc à…

Plus tiré par les cheveux (encore) : appels récursifs terminaux

Si vous jetez un œil aux codes sources que j’écris, il arrive que j’utilise des goto à la place de boucles classiques. Et oui, sacrilège § L’explication ? ’Tention, c’est tordu : cela modélise des appels récursifs terminaux simples.β

L’exemple typique est celui d’une fonction :

{
    T x;

re:
    if (...)
        ...
    if (...)
        ...
    if (...) {
        x = f(x, ...);
        goto re;
    }
}

Alors oui, on pourrait bien sûr écrire tout ça sous forme d’une boucle ou carrément utiliser l’appel récursif et prier pour que le compilateur C optimise.γ

C’est en vérité une question d’emphase, et ces questions là sont profondément subjectives. Une boucle mettrait moins l’accent sur l’action et davantage sur sa répétition. Dans le cas où le cas récursif est marginal, la construction s’en retrouve relativement maladroite : on a alors le choix entre une condition de boucle obscure et redondante, des marqueurs booléens dans tous les sens, ou des instructions break et continue judicieusement placées. En choisissant cette dernière solution, qui semble la moins contraignante, on se retrouve avec un code dans ce genre-là :

{
    T x;

    for (;;) {
        if (...)
            ...
        if (...)
            ...
        if (...) {
            x = f(x, ...);
            continue;
        }
        break;
    }
}

Mieux ? Je ne crois pas, mais je n’irai pas non plus cracher sur celui qui écrit cela. :) Tout cela au fond pour une question de…

β : Là aussi, soyons fous mais pas trop, on ne peut pas émuler la récursivité terminale dans le cas général avec goto.

γ : GCC le fait, m’enfin, d’autres compilateurs ne le font pas forcément, ce n’est pas une optimisation très orthodoxe en C comme elle peut l’être dans les langages fonctionnels, l’idiome étant en Cδ de tout convertir en style itératif.

δ : Mais rms a trop fait de Lisp, comme tout le monde sait. Lalala.

Lisibilité !

C’est là que les gens se fâchent, que les regards se clashent, que la violence se lâche, wash wash. Hum, les uns disent de ne pas utiliser goto, pour la clarté du code, et pour les mêmes raisons, d’autres l’utilisent ! C’est la vie. Après tout, il y bien des gens qui codent en style GNU en pensant se rendre lisibles. :)

La question de la lisibilité de goto tient principalement à sa nature la plus primaire : la possibilité de passer librement d’une portion de code à une autre. Les détracteurs de goto pointent du doigt les utilisations de l’instruction qui engendrent des transitions difficiles à suivre dans le flot. Ceux qui en défendent le gain visuel évoquent la possibilité offerte par goto de déporter des blocs de code d’un endroit à un autre. Cette astuce est principalement utilisée pour la gestion des erreurs et permet ainsi de dégager du traitement utile la gestion des cas de défaillance, rares en pratique et souvent triviaux (l’action à entreprendre étant, la plupart du temps, de faire remonter l’information telle qu’on la reçoit).

Pour conclure sur un dernier exemple, j’abuse souvent de cette propriété pour écrire des choses comme celles-ci :

{
    T *p;
    R *q;

    p = malloc(sizeof *p);
    q = malloc(sizeof *q);
    if (p == NULL || q == NULL)
        goto bad;

    ...
    return ...;

bad:
    free(q);
    free(p);
    return ...;
}

Mon style vous répugne ? N’hésitez pas à commenter. :)

Annexe : setjmp et ses copains

Si, de mon expérience, goto est généralement relativement bien reçu dans la communauté des programmeurs C dans son ensemble (excepté dans certains cercles, et dans l’enseignement, sans doute de peur de pervertir les étudiantsε), il en est autrement de setjmp, le « goto non local ».ζ

Il y a probablement plusieurs raisons à cela : la difficulté d’utilisation (cf. restrictions énoncée par la norme, dont la nécessité de déclarer toutes les variables locales manipulables après le saut volatile), mais aussi les divers problèmes lié à la conservation et la restauration des états : il faut pouvoir replacer de manière sûre le contexte au point où il était au moment de l’appel à setjmp. Cela implique la libération de mémoire et l’appel de destructeurs pour les données allouées dans l’intervalle, mais également des notions de plus bas niveau telles que la gestion des signaux (cf. sigsetjmp(3)). En plus de ça, setjmp avec toute cette complication engendre des performances moisies, en général.

setjmp est toutefois utilisé, parfois, pour implémenter des mécanismes d’exceptions, notamment dans la kazlibη (pour le C) et diverses implémentations portables de langages dynamiques (l’interpréteur interactif d’OCaml, si je me rappelle bien, et d’autres dont je ne me rappelle plus…).

ε : Il paraît que ce sont les éléments perturbateurs tels que moi qui tentent leurs petits camarades avec le côté obscur. :)

ζ : Notez que GCC possède également des goto non locaux et indirects grâce aux pointeurs d’étiquettes. Je ne m’étendrai pas à ce sujet (car je connais mal le GNU C). Plus d'informations dans le manuel de GCC.

η : Je ne vois plus dans Google la page Web correspondant. Le projet est peut-être bien décédé…

Annexe : omg l’optimisation qu’elle est bonne

On entend dire parfois que goto c’est pas bon pour les performances. Bon alors, dans l’absolu, hum, en fait il n’y a pas d’absolu. On pourrait dire que remplacer des structures de contrôle classiques par goto enlève de l’information sémantique, et bon, dans l’absolu, c’est vrai. La question est de savoir si cette information sémantique est pertinente d’une part, et si elle est utilisée, d’autre part. Question pertinence, c’est, là encore, un peu subjectif : est-ce que tel ou tel goto remplace simplement une combinaison intelligente de structures de contrôle ou ne reflète-t-il un concept absent du C ? C’est davantage une question philosophique, donc humaine, que d’importance à la machine… car en vérité, la plupart des compilateurs modernes ne tiennent absolument pas compte de ce genre de choses, et le langage intermédiaire employé n’emploie que des sauts. Ce sont ensuite les algorithmes d’analyse de flot (brrr, théorie des graphes) qui ont à leur charge de différencier les blocs et autres constructions utiles au compilateur.

[ tag:blog.huoc.org,2009:posts/29 ]
Voir les commentaires · Commenter

/\ \/

Le code et ses raisons : typedef en C

#. Par rz0 dans Le code et ses raisons. Mis à jour le 27.03 2010 à 02:40. 3 commentaires.
<. Le code et ses raisons : goto en C
c débutants idiomes pratique programmation typedef

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…

Aujourd’hui donc, au menu, typedef, le bien nommé, qui, très naturellement, permet de définir un type, c’est-à-dire lui associer un nouveau 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 :

  • typedef s’utilise comme une classe de stockage, dans une déclaration classique ;

  • à 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) ;

  • enfin, l’identificateur résultant de cette opération est un synonyme du type utilisé : l’ancien et le nouveau type sont utilisables de manière interchangeable.

typedef, pourquoi

Il y a maintes raisons pour lesquelles quelqu’un peut vouloir utiliser typedef, mais il est possible de les ranger en deux familles :

  • celles qui visent à simplifier l’écriture : p.ex. en renommant un pointeur de fonctions, ou en permettant d’omettre le mot-clé struct ou union ;

  • et celles qui introduisent une abstraction (p.ex. un type entier de taille variable selon l’architecture, ou encore un type opaque).

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 flemme, l’histoire a montré que de grands noms le soutiennent.

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.

typedef abusif ?

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.α Elle est entre autre explicitement déconseillée par le KNF d'OpenBSD.

La raison invoquée est la suivante : utiliser typedef 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, typedef masque la nature du type réel à l’utilisateur.

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 opaque, 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é struct ?

α : Pour information, je suis également de ceux qui s’y opposent.

Zoom sur l’idée d’abstraction

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 ?

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 : size_t en ANSI-C, off_t dans POSIX, ou encore les fameux intXX_t du C99.

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 Dict est une manière courtoise d’indiquer ses intentions : on n’a besoin que de cette interface-ci.

Typiquement, on se retrouve alors avec un type concret, disons struct htable, qui modélise une table de hachage, que l’on souhaite masquer derrière le type Dict. La bonne pratique du C veut que l’on passe en paramètre de nos fonctions un pointeur vers struct htable. La question qui divise est alors la suivante : faut-il utiliser typedef la structure ou le pointeur vers celle-ci ?

Ç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.

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.

Dans un camp

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.β

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é).

β : 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.

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.

Et dans l’autre

Cependant, si l’on opte pour le typedef 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.

À 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.

À 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.

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 pure 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. :)

Conclusion

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…

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 typedef 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.

Mais je ne suis pas pour autant fondamentalement opposé à l’usage de typedef 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 typedef. Une telle convention aurait du sens.

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.

[ tag:blog.huoc.org,2009:posts/37 ]
Voir les commentaires · Commenter

/\

7 recettes pour aller plus loin avec le préprocesseur C

#. Par rz0. Mis à jour le 15.03 2010 à 17:07. 5 commentaires.
c effets_de_bord généricité préprocesseur trucs_et_astuces

Ce soir, j’ai décidé d’être paresseux, et comme beaucoup de blogueurs, je vous sers ici un peu de précuit, un peu de réchauffé : une liste de trucs et astuces !

Mais pas n’importe laquelle ! Récemment, j’ai twitté sur le triste état des ressources disponibles sur le Web français apparaissant dans des recherches telles que « généricité C ». Il y a beaucoup trop de cours et autres tutos superficiels, destinés à inculquer aux débutants quelques bases du langage ou de la programmation. J’ai donc décidé de réagir, à mon échelle, en publiant sur mon modeste blog des articles sur le C pour les bons ! :-° Voici donc le premier : le préprocesseur C, pour les bons !

Je ne saurais être tenu responsable de l’utilisation que vous faites des techniques décrites ici. En particulier, si vous vous faites insulter pour code illisible ou quelque chose comme cela, il ne faudra pas venir vous plaindre. :]

Si en lisant ce qui suit, vous ne vous sentez pas très à l’aise, ou ne comprenez pas quelque chose, il vous manque peut-être quelques notions de base ; mais pas de panique, il vous suffit d’aller lire un cours quelconque, comme par exemple ce tuto dédié au préprocesseur, sur le SdZ.

La plupart des astuces que je présente ici sont illustrées dans des frameworks tels que COS. En moins violent, et plus usité, les en-têtes BSD `sys/queue.h` et `sys/tree.h`, dont je reparlerai probablement bientôt, sont également de bonnes illustrations de certaines techniques présentées ci-dessous. Citons aussi SGLIB, dans la même veine que sys/queue.h et sys/tree.h, mais poussant le concept un peu plus loin.

J’ai classé les astuces par ordre croissant de degré d’aliénation requis pour accepter de les utiliser. :-° Prêts ? Let’s rock!

Générez des séquences : opérateurs ,, ?:, && et ||

On vous a toujours dit que l’opérateur ,, c’était mal, que c’était Le Mal, après ‘goto‘. Mais il y a un cas où celui-ci peut s’avérer être un allié… intéressant. Il s’agit du contexte des macros.

Bien souvent, vous aurez envie que votre macro se comporte le plus possible comme une fonction, c’est-à-dire que si celle-ci renvoie une valeur, vous pouvez l’utiliser dans une expression.

C’est là que l’opérateur virgule (,) entre en jeu : il vous permet de placer plusieurs expressions dans votre macro, et que le tout soit réutilisable… comme une expression ! Les opérateurs ?:, && et || ajoutent un peu de variété à votre éventail de possibilités… mais rappelez-vous qu’il faut employer avec && ou || des opérandes à valeur entière (ou en tout cas qu’il est possible de convertir en entier).

Et un exemple bidon :

#define GETARG(argcptr, argvptr) (--*(argcptr), ++*(argvptr))
#define CMP(p, q) ((p) == (q) || cmpfunc((p), (q)) == 0)

Remarquez le problème des effets de bords potentiellement provoqués par l’évaluation de p ou q, dans la seconde macro ; nous allons y revenir…

Générez des structures de contrôle : utilisez la boucle for !

Mais les macros ne sont pas limitées à remplacer des fonctions, elles peuvent également être utilisées pour créer de nouvelles structures de contrôle.

À la différence d’un appel de fonction, l’utilisation d’une structure de contrôle inclut un (ou plusieurs) blocs de code. Le schéma de base simplifié est le suivant :

BEGIN (/* ... */) {
        /* ... */;
} END (/* ... */);

Il faut bien sûr remplacer BEGIN et END par des macros appropriées.

En utilisant des boucles for pour implémenter votre structure de contrôle, vous pouvez souvent omettre la partie END, et ainsi alléger l’usage de vos macros.

Un exemple de telles constructions peut être trouvé dans sys/queue.h ; dans cet exemple, on parcourt une liste (implémentation typique : une boucle for) :

LIST_FOREACH (var, head, next) {
        /* 'var' pointe successivement sur chaque élément. */
        /* ... */;
}

Un autre exemple, de structure plus complète, munie du marqueur de fin, peut être trouvé dans les bibliothèques standards de Plan 9 ; le code suivant permet de traiter les options de la ligne de commandes :

ARGBEGIN {
case 'a':
        /* Option '-a' spécifiée. */
        /* ... */;
        break;

case 'b':
        /* ... */;
        break;

default:
        usage();
} ARGEND;

Utilisez la concaténation pour simuler la généricité par nom

La concaténation est un mécanisme puissant puisqu’elle permet de générer des identificateurs à partir de fragments, dont certains peuvent être passés en paramètres à vos macros.

Moyennant le respect de quelques conventions dans les noms, vous pouvez écrire du code générique, dont les parties spécifiques sont masquées derrière un espace de nom improvisé passé en argument.

Et encore un exemple bidon pour illustrer le principe de base :

#define RELEASE(name, p) do {                               \
        if (name##_decrref(p) == 0)                         \
                free(p);                                    \
} while (/* CONSTCOND */ 0)

Générez des définitions

Rappelez-vous l’histoire des effets de bord que nous avons rencontrée plus haut. Il n’y a pas de méthode magique, pour éviter les effets de bords, il faut passer par des variables intermédiaires.

Une solution simple et plutôt élégante pour résoudre ce problème est de générer non plus du code directement, mais des fonctions… Mais, mais, me direz-vous, quel intérêt de passer par des macros pour générer des fonctions ? Pourquoi ne pas écrire les fonctions directement ?

Et bien, cette astuce, combinée à la précédente, permet un peu de généricité ! Et un ptit exemple pas très utile, pour la route :

#define ARRAY_FIND_PROTOTYPE(name, type, cmp)                 \
type *name##_ARRAY_FIND(type *, size_t, type *)

#define ARRAY_FIND_GENERATE(name, type, cmp)                  \
type *name##_ARRAY_FIND(type *_a, size_t _n, type *_elm)      \
{                                                             \
        for (; _n > 0; ++_a, --_n) {                          \
                if (cmp(_a, _elm) == 0)                       \
                        return _a;                            \
        }                                                     \
        return NULL;                                          \
}

#define ARRAY_FIND(name, a, n, elm)                           \
        name##_ARRAY_FIND((a), (n), (elm))

Pour des exemples plus complets, je vous invite à jeter un coup d’œil à l’implémentation de `sys/tree.h` (ou localement, dans /usr/include/sys/tree.h si vous avez un BSD sous la main).

Côté performances, vous n’avez guère de soucis à vous faire. Si vous définissez des fonctions statiques, le compilateur s’occupera tout seul comme un grand de les machiner comme il se doit. Et si vous voulez lui forcer un peu la main, inline est là pour ça.α

α : Mais c’est du C99… je reviendrai probablement sur cette question dans un prochain article.

Simulez les alternatives avec la concaténation

Nous arrivons ainsi à la dernière astuce accessible avec un préprocesseur à la norme C89.

Vous connaissez certainement les directives #if, #ifdef, et compagnie. Hélas, elles ne peuvent être utilisées à l’intérieur d’une définition de macro.

Dans les cas simples — mais très courants — où vous souhaitez simplement discriminer entre plusieurs valeurs d’une constante passée en paramètre, cependant, vous pouvez vous en sortir en utilisant… la concaténation ! Encore elle !

En effet, il suffit pour cela de définir chaque alternative comme une macro séparée, nommée avec un préfixe commun, et un suffixe dépendant du cas. Illustration :

#define DECL_STATIC_0
#define DECL_STATIC_1 static
#define DECL_STATIC(s) DECL_STATIC_##s

#define SOME_FUNCTION_PROTOTYPE(name, type, s)              \
DECL_STATIC(s) type some_function(type);

Simulez les opérations sur les n-uplets avec les macros variadic (C99)

Avec C99 ont été introduites les macros variadic, c’est-à-dire acceptant un nombre variable d’arguments. Cet ajout, a priori mineur, est en vérité très important, car il permet la manipulation des n-uplets, soit des collections de valeurs.

Pour nous, un n-uplet sera une suite d’éléments séparés par des virgules, entre parenthèses. Par exemple :

(a, b, c)

Pour opérer sur des tuples, le truc de base à remarquer est le suivant :

  • les appels de macros respectent l’équilibrage des parenthèses (ce qui permet effectivement de passer des n-uplets comme simples arguments à des macros) ;

  • les macros sont développées autant de fois que possibles : si le texte substitué par une macro contient encore des invocations de macros, celles-ci sont traitées (sauf si elles ont déjà été appelées) ;

  • en juxtaposant un n-uplet à un nom de macro, on obtient un appel de la macro correspondante, avec en guise d’arguments les éléments du n-uplet !

Il faudrait un billet entier pour explorer en profondeur toutes les possibilités offertes par cette astuce. Je ne vais présenter ici qu’un cas d’utilisation simple, mais sachez qu’il est possible, par exemple de créer une macro qui substitue tout n-uplet par 1 et toute autre construction par 0, par exemple ! Je vous laisse imaginer ce que l’on peut en faire, combinée aux alternatives par concaténation expliquées ci-dessus.

Mais revenons à notre modeste exemple, qui illustre la fonction identité de manière ridiculement complexe :

#define IDENTITY(...) __VA_ARGS__

/*
 * 'IDENTITY' peut en fait servir à supprimer des parenthèses
 * superflues gênantes.
 */
#define CALL_WITH_RESOURCE(name, var, aargs, f, args) do {  \
        name##_type var = name##_alloc aargs;               \
        f(var, IDENTITY args);                              \
        name##_free(var);                                   \
} while (/* CONSTCOND */ 0)

#define FILE_type FILE *
#define FILE_alloc fopen
#define FILE_free fclose

/* ... */
CALL_WITH_RESOURCE (FILE, fp, ("log.txt", "a"),
    fprintf, ("%d\n", 42));

COS contient une bibliothèque entière de macros travaillant sur les n-uplets. Je vous invite à regarder le code source si cela vous intéresse ; il s’agit plus particulièrement du dossier CosBase/include/cos/cpp/, dans l’archive.

Énumérez les cas et déroulez les appels pour émuler la récursion (C99)

Au vu de ce que nous venons de voir, vous êtes en droit de vous demander : « Peut-on faire pire ? » Et la réponse est oui ! :) J’ai gardé cette astuce pour la fin car elle constitue, à mes yeux, une limite que je ne souhaite pas, à titre personnel, franchir.

Un peu plus haut, j’ai dit quelque chose d’important : dans une chaîne de substitutions de macros, une macro déjà substituée ne le sera plus, même si elle apparaît dans le texte produit… il est donc impossible de faire des macros récursives !

L’astuce ici consiste à dire : « L’univers est fini, je vais décrire tout l’univers ! » Aidée de la concaténation, les possibilités sont vraiment (in)finies ! Mais je ne m’étendrai pas davantage sur le sujet. Encore une fois, COS contient toute une panoplie d’exemples, qui, je n’en doute pas, apparaîtront brillants pour certains, et affligeants pour d’autres.

Voilà, en espérant vous avoir appris quelques petits trucs rigolos ! Have fun!

[ tag:blog.huoc.org,2009:posts/46 ]
Voir les commentaires · Commenter

>> Page : 0 1