web-dev-qa-db-fra.com

Fichier d'en-tête inclus une seule fois dans tout le programme?

Je sais que c'est une question courante, mais je n'arrive toujours pas à comprendre complètement.

Dans un programme C ou C++ généré à partir de plusieurs fichiers source et d'en-tête différents, chaque fichier d'en-tête ne sera-t-il inclus qu'une seule fois dans le code entier lorsque les protections d'en-tête sont utilisées ?

Quelqu'un m'a dit précédemment qu'un fichier d'en-tête (avec des gardes d'inclusion) ne sera inclus qu'une seule fois dans une unité de traduction mais plusieurs fois dans le code entier. Est-ce vrai?

S'il n'est inclus qu'une seule fois dans tout le code, lorsqu'un fichier souhaite l'inclure et que le préprocesseur détecte qu'il a déjà été inclus, comment ce fichier qui souhaite l'utiliser sait-il où se trouve le code dans lequel il était précédemment inclus?

28
Engineer999

Voici le processus:

source           header   source header header
   \           /        \   |      /   /
    \         /          \  |     /   /
  PREPROCESSOR            PREPROCESSOR
       |                      |
       V                      V
 preprocessed code      preprocessed code
       |                      |
    COMPILER               COMPILER
       |                      |
       V                      V
  object code              object code
             \            /
              \          /
               \        /
                 LINKER
                   | 
                   V
               executable

prétraitement

#include est pour cette première étape. Il demande au préprocesseur de traiter le fichier spécifié et d'insérer le résultat dans la sortie.

Si A inclut B et C et B inclut C, la sortie du préprocesseur pour A inclura le texte traité de C deux fois.

Il s'agit d'un problème, car cela entraînera des déclarations en double. Un remède consiste à utiliser des variables de préprocesseur pour suivre si le code source a été inclus (alias gardes d'en-tête).

#ifndef EXAMPLE_H
#define EXAMPLE_H

// header contents

#endif

La première fois, EXAMPLE_H n'est pas défini et le préprocesseur évaluera le contenu du bloc ifndef/endif. La deuxième fois, il sautera ce bloc. Ainsi, la sortie traitée change , et les définitions ne sont incluses qu'une seule fois.

Ceci est si courant qu'il existe une directive non standard implémentée par certains compilateurs qui est plus courte et ne nécessite pas de choisir une variable de préprocesseur unique:

#pragma once

// header contents

Vous pouvez déterminer à quel point vous voulez que votre code C/C++ soit portable et quel protège-en-tête utiliser.

Les gardes d'en-tête s'assureront que le contenu de chaque fichier d'en-tête est présent au plus une fois dans le code prétraité d'une unité de traduction.

Compilation

Le compilateur génère du code machine à partir de votre C/C++ prétraité.

Généralement, les fichiers d'en-tête contiennent uniquement des déclarations, pas les définitions réelles (aka implémentations). Le compilateur inclut une table de symboles pour tout ce qui manque actuellement une définition.

Liaison

L'éditeur de liens combine les fichiers objets. Il fait correspondre les définitions (aka implémentations) avec les références à la table des symboles.

Il se peut que deux fichiers objets fournissent la définition, et l'éditeur de liens en prendra un. Cela se produit si vous avez mis du code exécutable dans vos en-têtes. Cela ne se produit généralement pas en C, mais cela se produit très fréquemment en C++, à cause des modèles.

L'en-tête "code", qu'il s'agisse de déclarations ou de définitions, est inclus plusieurs fois dans tous les fichiers objet mais l'éditeur de liens fusionne tout cela ensemble, de sorte qu'il n'est présent qu'une seule fois dans le exécutable. (J'exclus les fonctions intégrées, qui sont présentes plusieurs fois.)

29
Paul Draper

Un "fichier d'en-tête" est en fait inséré par le pré-processeur avant le début de la compilation. Pensez-y simplement comme "remplaçant" son #include directive.

Le garde ...

#ifndef MY_HEADER_H
#define MY_HEADER_H

....

#endif

... est exécuté après le remplacement. Ainsi, l'en-tête peut en fait être inclus plusieurs fois, mais la partie "protégée" du texte n'est transmise au compilateur qu'une seule fois par le préprocesseur.

Donc, s'il y a des définitions de génération de code dans l'en-tête, elles seront - bien sûr - incluses dans le fichier objet de l'unité de compilation (aka "module"). Si le même en-tête est #includeded dans plusieurs modules, ceux-ci apparaîtront plusieurs fois.

Pour les définitions static, cela ne pose aucun problème, car elles ne seront pas visibles au-delà de la portée du fichier du module (aka ). Pour les définitions globales de programme, cela est différent et entraînera une erreur "définitions multiples".

Remarque: c'est principalement pour C . Pour C++, il existe des différences importantes, car les classes, etc. ajoutent une complexité supplémentaire à quoi/quand plusieurs objets globaux sont autorisés.

16

Un fichier d'en-tête avec inclure les gardes sera inclus uniquement une fois par unité de traduction . Strictement parlant, cela peut être inclus plusieurs fois, mais les parties entre le préprocesseur #ifndef et #endif sera ignoré lors des inclusions ultérieures. Si cela est fait correctement, cela devrait être la totalité (ou la plupart) du fichier.

Une unité de traduction correspond généralement à un "fichier source", bien que certaines implémentations obscures puissent utiliser une définition différente. Si un fichier source compilé séparément comprend le même en-tête, le préprocesseur a aucun moyen de savoir qu'un autre fichier l'a déjà inclus, ou que tout autre fichier faisait partie du même projet.

Notez que lorsque vous venez de lier plusieurs fichiers source (unités de traduction) en un seul binaire, vous pouvez rencontrer des problèmes avec plusieurs définitions si l'en-tête ne se composent uniquement de déclarations, de modèles, de définitions de fonctions marquées inline ou de définitions de variables statiques. Pour éviter cela, vous devez déclarer les fonctions dans l'en-tête et les définir dans un fichier source séparé, que vous liez avec vos autres fichiers source.

7
Andrew

Le fichier d'en-tête sera inclus une fois par unité de traduction, oui. Il peut être inclus plusieurs fois par programme, car chaque unité de traduction est traitée séparément pour le processus de compilation. Ils sont réunis au cours du processus de liaison pour former un programme complet.

5
Bryan Shaw