web-dev-qa-db-fra.com

Dois-je utiliser #include dans les en-têtes?

Est-il nécessaire de #include un fichier, si à l'intérieur d'un en-tête (* .h), les types définis dans ce fichier sont utilisés?

Par exemple, si j'utilise GLib et souhaite utiliser le type de base gchar dans une structure définie dans mon en-tête, est-il nécessaire de faire un #include <glib.h>, sachant que je l'ai déjà dans mon fichier * .c?

Si oui, dois-je également le mettre entre le #ifndef et #define ou après le #define?

68
Victor

Les règles Goddard Space Flight Center de la NASA ( GSFC ) pour les en-têtes en C stipulent qu'il doit être possible d'inclure un en-tête dans un fichier source comme seul en-tête, et ce code utilisant les fonctionnalités fournies par cet en-tête sera alors compilé.

Cela signifie que l'en-tête doit être autonome, idempotent et minimal:

  • autonome - tous les types nécessaires sont définis en incluant les en-têtes appropriés si nécessaire.
  • idempotent - les compilations ne cassent pas même si elles sont incluses plusieurs fois.
  • minimal - il ne définit rien qui ne soit pas requis par le code qui utilise l'en-tête pour accéder aux fonctionnalités définies par l'en-tête.

L'avantage de cette règle est que si quelqu'un doit utiliser l'en-tête, il n'a pas à se battre pour déterminer quels autres en-têtes doivent également être inclus - il sait que l'en-tête fournit tout le nécessaire.

L'inconvénient possible est que certains en-têtes peuvent être inclus plusieurs fois; c'est pourquoi les gardes d'en-tête d'inclusion multiples sont cruciales (et pourquoi les compilateurs essaient d'éviter de réinclure les en-têtes dans la mesure du possible).

La mise en oeuvre

Cette règle signifie que si l'en-tête utilise un type - tel que 'FILE *' ou 'size_t '- il doit alors s'assurer que l'autre en-tête approprié (<stdio.h> ou <stddef.h> par exemple) doit être inclus. Un corollaire, souvent oublié, est que l'en-tête ne doit inclure aucun autre en-tête qui soit pas nécessaire à l'utilisateur du package pour utiliser le package. L'en-tête doit être minimal, en d'autres termes.

De plus, les règles GSFC fournissent une technique simple pour s'assurer que c'est ce qui se passe:

  • Dans le fichier source qui définit la fonctionnalité, l'en-tête doit être le premier en-tête répertorié.

Par conséquent, supposons que nous ayons un tri magique.

magicsort.h

#ifndef MAGICSORT_H_INCLUDED
#define MAGICSORT_H_INCLUDED

#include <stddef.h>

typedef int (*Comparator)(const void *, const void *);
extern void magicsort(void *array, size_t number, size_t size, Comparator cmp);

#endif /* MAGICSORT_H_INCLUDED */

magicsort.c

#include <magicsort.h>

void magicsort(void *array, size_t number, size_t size, Comparator cmp)
{
    ...body of sort...
}

Notez que l'en-tête doit inclure un en-tête standard qui définit size_t; le plus petit en-tête standard qui le fait est <stddef.h>, bien que plusieurs autres le fassent également (<stdio.h>, <stdlib.h>, <string.h>, éventuellement quelques autres).

De plus, comme mentionné précédemment, si le fichier d'implémentation a besoin d'autres en-têtes, qu'il en soit ainsi, il est tout à fait normal que des en-têtes supplémentaires soient nécessaires. Mais le fichier d'implémentation ('magicsort.c') doit les inclure lui-même, et ne pas s'appuyer sur son en-tête pour les inclure. L'en-tête ne doit inclure que ce dont les utilisateurs du logiciel ont besoin; pas ce dont les exécutants ont besoin.

En-têtes de configuration

Si votre code utilise un en-tête de configuration (GNU Autoconf et le "config.h" généré, par exemple), vous devrez peut-être l'utiliser dans "magicsort.c":

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include "magicsort.h"

...

C'est la seule fois que je sache que l'en-tête privé du module n'est pas le tout premier en-tête du fichier d'implémentation. Cependant, l'inclusion conditionnelle de "config.h" devrait probablement se trouver dans "magicsort.h" lui-même.


Mise à jour 2011-05-01

L'URL liée ci-dessus n'est plus fonctionnelle (404). Vous pouvez trouver la norme C++ (582-2003-004) sur EverySpec.com ; la norme C (582-2000-005) semble manquer en action.

Les lignes directrices de la norme C étaient les suivantes:

§2.1 UNITÉS

(1) Le code doit être structuré en unités ou en fichiers d'en-tête autonomes.

(2) Une unité doit être constituée d'un seul fichier d'en-tête (.h) et d'un ou plusieurs fichiers de corps (.c). Collectivement, les fichiers d'en-tête et de corps sont appelés fichiers source.

(3) Un fichier d'en-tête d'unité contient toutes les informations pertinentes requises par une unité cliente. Le client d'une unité doit accéder uniquement au fichier d'en-tête pour utiliser l'unité.

(4) Le fichier d'en-tête d'unité doit contenir des instructions #include pour tous les autres en-têtes requis par l'en-tête d'unité. Cela permet aux clients d'utiliser une unité en incluant un seul fichier d'en-tête.

(5) Le fichier de corps d'unité doit contenir une instruction #include pour l'en-tête d'unité, avant toutes les autres instructions #include. Cela permet au compilateur de vérifier que toutes les instructions #include requises se trouvent dans le fichier d'en-tête.

(6) Un fichier corporel ne doit contenir que des fonctions associées à une unité. Un fichier de corps peut ne pas fournir d'implémentations pour les fonctions déclarées dans différents en-têtes.

(7) Toutes les unités clientes qui utilisent une partie d'une unité U donnée doivent inclure le fichier d'en-tête de l'unité U; cela garantit qu'il n'y a qu'un seul endroit où les entités de l'unité U sont définies. Les unités clientes peuvent appeler uniquement les fonctions définies dans l'en-tête de l'unité; ils ne peuvent pas appeler des fonctions définies dans le corps mais non déclarées dans l'en-tête. Les unités clientes ne peuvent pas accéder aux variables déclarées dans le corps mais pas dans l'en-tête.

Un composant contient une ou plusieurs unités. Par exemple, une bibliothèque mathématique est un composant qui contient plusieurs unités telles que le vecteur, la matrice et le quaternion.

Les fichiers d'en-tête autonomes n'ont pas de corps associés; par exemple, un en-tête de types communs ne déclare pas de fonctions, il n'a donc pas besoin de corps.

Quelques raisons d'avoir plusieurs fichiers de corps pour une unité:

  • Une partie du code du corps dépend du matériel ou du système d'exploitation, mais le reste est courant.
  • Les fichiers sont trop volumineux.
  • L'unité est un ensemble d'utilitaires courants et certains projets n'utiliseront que quelques-unes des fonctions. Le fait de placer chaque fonction dans un fichier séparé permet à l'éditeur de liens d'exclure celles qui ne sont pas utilisées de l'image finale.

§2.1.1 L'en-tête comprend une justification

Cette norme requiert que l'en-tête d'une unité contienne #include instructions pour tous les autres en-têtes requis par l'en-tête d'unité. Placement #include pour l'en-tête d'unité en premier dans le corps de l'unité permet au compilateur de vérifier que l'en-tête contient tout le nécessaire #include déclarations.

Une conception alternative, non autorisée par cette norme, ne permet pas #include déclarations dans les en-têtes; tout #includes sont effectués dans les fichiers corps. Les fichiers d'en-tête d'unité doivent alors contenir #ifdef des instructions qui vérifient que les en-têtes requis sont inclus dans le bon ordre.

Un avantage de la conception alternative est que le #include liste dans le fichier corps est exactement la liste de dépendances nécessaire dans un makefile, et cette liste est vérifiée par le compilateur. Avec la conception standard, un outil doit être utilisé pour générer la liste de dépendances. Cependant, tous les environnements de développement recommandés par la branche fournissent un tel outil.

Un inconvénient majeur de la conception alternative est que si la liste d'en-tête requise d'une unité change, chaque fichier qui utilise cette unité doit être modifié pour mettre à jour le #include liste d'instructions. En outre, la liste d'en-tête requise pour une unité de bibliothèque de compilateur peut être différente sur différentes cibles.

Un autre inconvénient de la conception alternative est que les fichiers d'en-tête de bibliothèque du compilateur et les autres fichiers tiers doivent être modifiés pour ajouter le #ifdef déclarations.

Une autre pratique courante consiste à inclure tous les fichiers d'en-tête système avant tous les fichiers d'en-tête de projet, dans les fichiers corps. Cette norme ne suit pas cette pratique, car certains fichiers d'en-tête de projet peuvent dépendre des fichiers d'en-tête système, soit parce qu'ils utilisent les définitions de l'en-tête système, soit parce qu'ils souhaitent remplacer une définition système. Ces fichiers d'en-tête de projet doivent contenir #include instructions pour les en-têtes du système; si le corps les inclut d'abord, le compilateur ne vérifie pas cela.

Norme GSFC disponible via Internet Archive 2012-12-10

Informations courtoisie Eric S. Bullington :

La norme de codage NASA C référencée est accessible et téléchargeable via les archives Internet:

http://web.archive.org/web/20090412090730/http://software.gsfc.nasa.gov/assetsbytype.cfm?TypeAsset=Standard

Séquençage

La question demande également:

Si oui, dois-je aussi le mettre (le #include lignes) entre les #ifndef et #define ou après le #define.

La réponse montre le mécanisme correct - les inclus imbriqués, etc., devraient être après le #define (et le #define devrait être la deuxième ligne de non-commentaire dans l'en-tête) - mais cela n'explique pas pourquoi c'est correct.

Considérez ce qui se passe si vous placez le #include entre le #ifndef et #define. Supposons que l'autre en-tête lui-même comprenne divers en-têtes, peut-être même #include "magicsort.h" indirectement. Si la deuxième inclusion de magicsort.h survient avant #define MAGICSORT_H_INCLUDED, puis l'en-tête sera inclus une seconde fois avant que les types qu'il définit ne soient définis. Ainsi, en C89 et C99, tout nom de type typedef sera redéfini par erreur (C2011 leur permet d'être redéfini avec le même type), et vous obtiendrez les frais généraux de traitement le fichier plusieurs fois, ce qui va à l'encontre de l'objectif du protège-en-tête en premier lieu. C'est aussi pourquoi le #define est la deuxième ligne et n'est pas écrit juste avant le #endif. La formule donnée est fiable:

#ifndef HEADERGUARDMACRO
#define HEADERGUARDMACRO

...original content of header — other #include lines, etc...

#endif /* HEADERGUARDMACRO */
95
Jonathan Leffler

Une bonne pratique consiste à ne mettre #includes dans un fichier include que si le fichier include en a besoin. Si les définitions d'un fichier include donné ne sont utilisées que dans le fichier .c, ne l'incluez que dans le fichier .c.

Dans votre cas, je l'inclurais dans le fichier d'inclusion entre les # ifdef/# endif.

Cela minimisera les dépendances afin que les fichiers qui n'ont pas besoin d'une inclusion donnée n'aient pas à être recompilés si le fichier d'inclusion change.

20
Richard Pennington

J'utilise la construction suivante pour être sûr que le fichier include nécessaire est inclus avant cette inclusion. J'inclus tous les fichiers d'en-tête dans les fichiers source uniquement.

#ifndef INCLUDE_FILE_H
 #error "#include INCLUDE.h" must appear in source files before "#include THISFILE.h"
#endif
0
Thomas Weber

Lors de la compilation, le préprocesseur remplace simplement la directive #include par le contenu de fichier spécifié. Pour éviter une boucle sans fin, il doit utiliser

#ifndef SOMEIDENTIFIER
#define SOMEIDENTIFIER
....header file body........
#endif

Si un en-tête a été inclus dans un autre en-tête qui a été inclus dans votre fichier, il n'est pas nécessaire de l'inclure explicitement à nouveau, car il sera inclus dans le fichier de manière récursive

0
Alex

Oui, c'est nécessaire ou le compilateur se plaindra lorsqu'il essaiera de compiler du code dont il n'est pas "au courant". Pensez aux # include sont un indice/Nudge/elbow au compilateur pour lui dire de récupérer les déclarations, les structures, etc. afin de réussir la compilation. L'astuce d'en-tête # ifdef/# endif comme l'a souligné jldupont, est d'accélérer la compilation du code.

Il est utilisé dans les cas où vous avez un compilateur C++ et compilez du code C simple comme indiqué ici Voici un exemple de l'astuce:

 # ifndef __MY_HEADER_H __ 
 # définir __MY_HEADER_H __ 
 
 # ifdef __cplusplus 
 extern "C" {
 # endif 
 
 
/* Code C tel que structures, déclarations, etc. */
 
 # Ifdef __cplusplus 
} 
 # endif 
 
 # endif/* __MY_HEADER_H__ */

Maintenant, si cela a été inclus plusieurs fois, le compilateur ne l'inclura qu'une seule fois puisque le symbole __MY_HEADER_H__ est défini une fois, ce qui accélère les temps de compilation. Remarquez le symbole cplusplus dans l'exemple ci-dessus, c'est la manière standard normale de gérer la compilation C++ si vous avez un code C qui traîne.

J'ai inclus ce qui précède pour le montrer (bien que cela ne soit pas vraiment pertinent pour la question originale de l'affiche). J'espère que cela vous aidera, Cordialement, Tom.

PS: Désolé d'avoir laissé quelqu'un voter contre cela, car je pensais que cela serait utile pour les nouveaux arrivants en C/C++. Laissez un commentaire/des critiques, etc., car ils sont les bienvenus.

0
t0mm13b

Vous devez inclure l'en-tête de votre en-tête, et il n'est pas nécessaire de l'inclure dans le .c. Les inclusions doivent aller après la # définition pour ne pas être inclus plusieurs fois inutilement. Par exemple:

/* myHeader.h */
#ifndef MY_HEADER_H
#define MY_HEADER_H

#include <glib.h>

struct S
{
    gchar c;
};

#endif /* MY_HEADER_H */

et

/* myCode.c */
#include "myHeader.h"

void myFunction()
{
    struct S s;
    /* really exciting code goes here */
}
0
danio

Habituellement, les développeurs de bibliothèques protègent leurs inclusions de multiples, y compris avec la "astuce" #ifndef/# define/#endif, afin que vous n'ayez pas à le faire.

Bien sûr, vous devriez vérifier ... mais de toute façon le compilateur vous le dira à un moment donné ;-) C'est de toute façon une bonne pratique pour vérifier les inclusions multiples car cela ralentit le cycle de compilation.

0
jldupont

Incluez simplement tous les en-têtes externes dans n fichier d'en-tête commun dans votre projet, par exemple global.h et incluez-le dans tous vos fichiers c:

Cela peut ressembler à ceci:

#ifndef GLOBAL_GUARD
#define GLOBAL_GUARD

#include <glib.h>
/*...*/
typedef int  YOUR_INT_TYPE;
typedef char YOUR_CHAR_TYPE;
/*...*/
#endif

Ce fichier utilise notamment guard pour éviter les inclusions multiples, les définitions multiples illégales, etc.

0
psihodelia