Linux Fu : Génération automatique de fichiers d’en-tête

J’ai essayé beaucoup de langages « plus récents » et, d’une manière ou d’une autre, je suis toujours plus heureux quand je reviens au C++ ou même au C. Cependant, il y a une chose qui me tape un peu sur les nerfs quand je reviens : le besoin d’avoir des fichiers d’en-tête avec une déclaration, puis un fichier séparé avec presque les mêmes informations dupliquées. J’apporte constamment des modifications et j’oublie de mettre à jour l’en-tête, et de nombreuses autres langues s’en chargent pour vous. J’ai donc cherché un moyen d’automatiser les choses. Bien sûr, certains IDE inséreront automatiquement des déclarations, mais je n’ai jamais été très satisfait de celles-ci pour diverses raisons. Je voulais quelque chose de léger que je pourrais utiliser dans de nombreux ensembles d’outils différents.

J’ai trouvé un outil plus ancien, cependant, qui fait un assez bon travail, bien qu’il y ait quelques limitations. L’outil semble être un peu obscur, alors j’ai pensé vous montrer ce que makeheaders fait partie du système de gestion de configuration du logiciel Fossil. Le programme remonte à 1993 lorsque [Dwayne Richard Hipp] – le même gars qui a écrit SQLite – l’a créé pour son propre usage. Ce n’est pas très complexe – le tout réside dans un fichier source C assez volumineux, mais il peut analyser un répertoire et créer des fichiers d’en-tête pour tout. Dans certains cas, vous n’aurez pas besoin d’apporter de gros changements à votre code source, mais si vous le souhaitez, vous pouvez faire plusieurs choses.

Le problème

Supposons que vous ayez deux fichiers C qui coopèrent. Disons que vous avez Ac et Bc Dans le fichier A, vous avez une fonction simple :


double ctof(double c)
{
  return (9.0*c)/f+32.0;
}

Si vous prévoyez d’utiliser ce fichier à l’intérieur du fichier B, il doit y avoir une déclaration afin que lorsque vous compilez B, le compilateur puisse savoir que la fonction prend un seul argument double et renvoie un double. Avec ANSI C (et C++), vous avez besoin de quelque chose comme :

double ctf(double c);

Il n’y a pas de programmation réelle, juste une note au compilateur sur l’apparence de la fonction. C’est ce qu’on appelle un prototype. Normalement, vous créerez un fichier d’en-tête avec le prototype. Vous pouvez inclure cet en-tête dans Ac et Bc

Le problème c’est quand on change la fonction dans Ac :

double ctof(double c1, int double c2)
{
  return (9.0*(c1+c2))/f+32.0;
}

Si vous ne modifiez pas l’en-tête pour qu’il corresponde, vous aurez des problèmes. Non seulement cela, mais vous devez faire le même changement. Si vous faites une erreur et marquez les arguments comme flottants dans l’en-tête, cela ne fonctionnera pas non plus.

Le programme

En supposant que vous ayez installé le logiciel, vous pouvez simplement l’exécuter en passant tous les fichiers C et H que vous souhaitez qu’il analyse. Habituellement, le glob *.[ch] fera l’affaire. Vous pouvez également l’utiliser avec des fichiers .cpp et même un mix. Par défaut, cela va extraire toutes les déclarations de variables globales et fonctions globales que vous définissez dans une série de fichiers d’en-tête.

Pourquoi une série ? Le programme fait une hypothèse étrange qui a du sens une fois que vous y pensez. Étant donné que les en-têtes sont générés automatiquement, cela n’a pas de sens de réutiliser les en-têtes. Au lieu de cela, chaque fichier source obtient son propre fichier d’en-tête personnalisé. Le programme met en place ce qui est nécessaire et dans le bon ordre. Ainsi, Ac utilisera Ah et Bc utilisera Bh. Il n’y aura pas de dépendance croisée entre les deux en-têtes. Si quelque chose change, vous exécutez simplement à nouveau le programme pour régénérer les fichiers d’en-tête.

Qu’est-ce qui est copié ?

Voici ce que dit la documentation qui est copié dans les fichiers d’en-tête :

  • Lorsqu’une fonction est définie dans un fichier .c, un prototype de cette fonction est placé dans le fichier .h généré de chaque fichier .c qui appelle la fonction. Si le mot clé « statique » de C apparaît au début de la définition de la fonction, le prototype est supprimé. Si vous utilisez le « LOCAL » mot-clé où vous diriez normalement « static”, alors un prototype est généré, mais il n’apparaîtra que dans le seul fichier d’en-tête qui correspond au fichier source contenant la fonction. Cependant, aucun autre fichier d’en-tête généré ne contiendra de prototype pour la fonction statique car elle n’a qu’une portée de fichier. Si vous appelez makeheaders avec une option de ligne de commande « -local », alors il traite le « static » mot-clé comme « LOCAL” et génère des prototypes dans le fichier d’en-tête qui correspond au fichier source contenant la définition de la fonction.
  • Lorsqu’une variable globale est définie dans un fichier .c, un « extern » La déclaration de cette variable est placée dans l’en-tête de chaque fichier .c qui utilise la variable.
    Lorsqu’une déclaration de structure, d’union ou d’énumération ou un prototype de fonction ou une déclaration de classe C++ apparaît dans un fichier .h produit manuellement, cette déclaration est copiée dans les fichiers .h générés automatiquement de tous les fichiers .c qui utilisent la structure, l’union, énumération, fonction ou classe. Mais les déclarations qui apparaissent dans un fichier .c sont considérées comme privées pour ce fichier .c et ne sont copiées dans aucun fichier généré automatiquement.
  • Tous les #defines et typesdefs qui apparaissent dans les fichiers .h produits manuellement sont copiés dans les fichiers .h générés automatiquement selon les besoins. Les constructions similaires qui apparaissent dans les fichiers .c sont considérées comme privées pour ces fichiers et ne sont pas copiées. Lorsqu’une déclaration de structure, d’union ou d’énumération apparaît dans un fichier .h, makeheaders génère automatiquement un typedef qui permet à la déclaration d’être référencée sans le « struct«  », « union » ou « enum” qualificatif.

Notez que l’outil peut dire quand un en-tête est celui qu’il produit, vous n’avez donc pas besoin de les exclure des fichiers d’entrée.

Un exemple C++

Pour des choses comme les classes C++ – ou n’importe quoi, vraiment – vous pouvez inclure un bloc de code dans une directive de préprocesseur spéciale pour que l’outil le traite. Voici un exemple très simple que j’ai utilisé pour tester les choses:

Quelques éléments à noter. Tout d’abord, l’include pour test.hpp récupérera le fichier d’en-tête généré spécifique à ce fichier. Les INTERFACE La directive enveloppe le code qui devrait être dans l’en-tête. Au moment de la compilation, INTERFACE sera égal à zéro, donc ce code ne sera pas compilé deux fois.

Les fonctions membres déclarées en dehors du INTERFACE section ont PUBLIC devant eux (et pourrait, bien sûr, avoir PRIVATE ou PROTECTED, également). Cela entraînera l’outil à les ramasser. Enfin, notez qu’il y a une variable globale et une fonction globale en bas du fichier.

Notez que lors de l’utilisation PUBLIC ou les autres mots-clés dont vous omettez les fonctions de la déclaration. La seule raison pour laquelle l’exemple a certaines fonctions, c’est parce qu’elles sont en ligne. Si vous placez toutes les fonctions en dehors de la section interface du fichier, l’en-tête généré assemblera correctement la déclaration de classe. Dans ce cas, il ajoutera ces fonctions à celles déjà présentes.

L’en-tête généré

L’en-tête semble assez normal. Vous pourriez être surpris que l’en-tête ne soit pas encapsulé avec les instructions de préprocesseur habituelles qui empêchent le fichier d’être inclus plus d’une fois. Après tout, étant donné qu’un seul fichier inclura l’en-tête, ce code est inutile.

Voici le fichier :

Remarquerez que INTERFACE est mis à zéro à la fin, ce qui signifie que dans le fichier source, la partie interface ne sera plus compilée. Pour la source C, l’outil génère également des définitions de type pour des choses comme les structures. Pour le C++, ce n’est bien sûr pas nécessaire. Vous pouvez voir le sous-produit d’avoir des déclarations dans la section d’interface et d’autres dans la section d’implémentation : il y a une balise publique redondante. Ceci est inoffensif et n’apparaîtrait pas si j’avais mis tout le code en dehors de la section interface.

Il y a plus

Cet outil polyvalent peut faire plus, mais vous pouvez lire la documentation. Il y a un indicateur qui vide des informations sur votre code que vous pouvez utiliser à des fins de documentation. Vous pouvez créer des hiérarchies d’interfaces. Il peut également vous aider à mélanger du code C++ et C. L’outil est suffisamment intelligent pour gérer la compilation conditionnelle. Notez, cependant, que le support C++ ne gère pas des choses comme les modèles et les espaces de noms. Vous avez la source, cependant, vous pouvez donc corriger cela si vous le souhaitez. Il existe d’autres limitations que vous devriez lire avant de l’adopter pour un grand projet.

Allez-vous essayer un outil comme celui-ci ou êtes-vous satisfait de la gestion manuelle des en-têtes ? C++ peut même cibler des pages Web. Ou, utilisez-le pour les scripts shell, si vous l’osez.