Comment structurer #includes en C

Supposons que j’ai un programme C qui se décompose en un ensemble de fichiers * .c et * .h. Si le code d’un fichier utilise des fonctions d’un autre fichier, où dois-je inclure le fichier d’en-tête? Dans le fichier * .c qui utilisait la fonction ou dans l’en-tête de ce fichier?

Par exemple, le fichier foo.c inclut foo.h , qui contient toutes les déclarations de foo.c ; idem pour bar.c et bar.h La fonction foo1() dans foo.c appelle bar1() , qui est déclarée dans bar.h et définie dans bar.c Maintenant, la question est, devrais-je inclure bar.h dans foo.h , ou dans foo.c ?

Quel serait un bon ensemble de règles empiriques pour de telles questions?

Vous devriez inclure foo.h dans foo.c. De cette façon, les autres fichiers c incluant foo.h ne porteront pas inutilement bar.h. Voici mon conseil pour inclure des fichiers d’en-tête:

  • Ajoutez des définitions d’inclusion dans les fichiers c – de cette manière, les dépendances de fichiers sont plus évidentes lors de la lecture du code.
  • Divisez le fichier foo.h en deux fichiers distincts, par exemple, foo_int.h et foo.h. Le premier porte les déclarations de type et de transmission nécessaires uniquement par foo.c. Le foo.h inclut les fonctions et les types nécessaires aux modules externes. Cela ressemble à la section privée et publique de foo.
  • Évitez les références croisées, c’est-à-dire les références foo et les références foo. Cela peut causer des problèmes de liaison et est également un signe de mauvaise conception

Comme d’autres l’ont noté, un en-tête foo.h devrait déclarer les informations nécessaires pour pouvoir utiliser les fonctionnalités fournies par un fichier source foo.c. Cela inclurait les types, énumérations et fonctions fournies par foo.c. (Vous n’utilisez pas de variables globales, n’est-ce pas? Si vous les utilisez, elles sont également déclarées dans foo.h.)

L’en-tête foo.h devrait être autonome et idempotent. Autonome signifie que tout utilisateur peut inclure foo.h sans avoir à se soucier des autres en-têtes nécessaires (car foo.h inclut ces en-têtes). Idempotent signifie que si l’en-tête est inclus plus d’une fois, aucun dommage n’est causé. Cela est réalisé par la technique classique:

  #ifndef FOO_H_INCLUDED #define FOO_H_INCLUDED ...rest of the contents of foo.h... #endif /* FOO_H_INCLUDED */ 

La question posée:

Le fichier foo.c comprend foo.h, qui contient toutes les déclarations de foo.c; idem pour bar.c et bar.h. La fonction foo1 () dans foo.c appelle bar1 (), qui est déclarée dans bar.h et définie dans bar.c. Maintenant, la question est, devrais-je inclure bar.h dans foo.h, ou dans foo.c?

Cela dépendra de savoir si les services fournis par foo.h dépendent de bar.h ou non. Si d’autres fichiers utilisant foo.h ont besoin de l’un des types ou des énumérations définis par bar.h pour pouvoir utiliser la fonctionnalité de foo.h, alors foo.h doit s’assurer que bar.h est inclus (en l’incluant). Cependant, si les services de bar.h ne sont utilisés que dans foo.c et ne sont pas nécessaires pour ceux qui utilisent foo.h, alors foo.h ne doit pas inclure bar.h

Je n’inclurais que les fichiers d’en-tête dans un fichier * .h requirejs pour le fichier d’en-tête lui-même. Les fichiers d’en-tête nécessaires à un fichier source devraient, à mon avis, être inclus dans le fichier source afin que les dépendances soient évidentes à partir de la source. Les fichiers d’en-tête doivent être conçus pour gérer les inclusions multiples afin que vous puissiez les insérer dans les deux si nécessaire pour plus de clarté.

J’inclus le jeu d’en-têtes le plus minimal possible dans le fichier .h et j’inclus le rest dans le fichier .c . Cela a parfois l’avantage de réduire les temps de compilation. Dans votre exemple, si foo.h n’a pas vraiment besoin de bar.h mais l’inclut quand même, et qu’un autre fichier inclut foo.h , alors ce fichier sera recompilé si bar.h change, même s’il n’a pas réellement besoin ou utilisez bar.h

Le fichier .h doit définir l’interface publique (c’est-à-dire l’api) avec les fonctions du fichier .c.

Si l’interface de file1 utilise l’interface de file2, alors #include file2.h dans file1.h

Si l’implémentation dans fichier1.c utilise des éléments de fichier2.c, alors fichier1.c doit inclure #include fichier2.h.

Je dois cependant admettre que – parce que j’ai toujours #include file1.h dans file1.c – normalement, je ne me dérangerais pas #including file2.h directement dans file1.c si c’était déjà #included dans file1.h

Si vous vous retrouvez un jour dans la situation où deux fichiers .c se présentent mutuellement .h, c’est un signe que la modularité est en panne et que vous devez envisager un peu de restructuration.

En utilisant les exemples de foo.c et foo.h j’ai trouvé ces instructions utiles:

  • Rappelez-vous que l’objective de foo.h est de faciliter l’utilisation de foo.c , foo.c donc à ce qu’il soit aussi simple, organisé et foo.c que possible. Soyez généreux avec des commentaires qui expliquent comment et quand utiliser les fonctionnalités de foo.c – et quand ne pas les utiliser.

  • foo.h déclare les fonctionnalités publiques de foo.c : fonctions, macros, typedefs et variables globales (frémissantes).

  • foo.c devrait foo.c #include "foo.h – voir la discussion, ainsi que le commentaire de Jonathan Leffler ci-dessous.

  • Si foo.c nécessite des en-têtes supplémentaires pour sa compilation, incluez-les dans foo.c

  • Si la compilation de foo.h nécessite des en-têtes externes, incluez-les dans foo.h

  • Utilisez le préprocesseur pour éviter que foo.h ne soit inclus plusieurs fois. (Voir ci-dessous.)

  • Si, pour une raison quelconque, un en-tête externe est requirejs pour qu’un autre fichier foo.c puisse utiliser les fonctionnalités de foo.c , incluez-le dans foo.h pour que le prochain développeur ne soit plus foo.h à un débogage inutile. Si vous êtes opposé à cela, pensez à append une macro qui affichera des instructions au moment de la compilation si les en-têtes requirejs n’ont pas été inclus.

  • N’incluez pas de fichier .c dans un autre fichier .c sauf si vous avez une très bonne raison et documentez-le clairement.

Comme l’a noté kgiannakakis, il est utile de séparer l’interface publique des définitions et déclarations nécessaires uniquement dans le foo.c lui-même. Mais au lieu de créer deux fichiers, il est parfois préférable de laisser le préprocesseur le faire pour vous:

 // foo.c #define _foo_c_ // Tell foo.h it's being included from foo.c #include "foo.h" . . . 

 // foo.h #if !defined(_foo_h_) // Prevent multiple inclusion #define _foo_h_ // This section is used only internally to foo.c #ifdef _foo_c_ . . . #endif // Public interface continues to end of file. #endif // _foo_h_ // Last-ish line in foo.h