Pourquoi un compilateur ne peut-il pas éviter d'importer deux fois un fichier d'en-tête par lui-même?

13

Nouveau en C ++! Je lisais donc ceci: http://www.learncpp.com/cpp-tutorial/110-a-first-look-at-the-preprocessor/

Protecteurs d'en-tête

Étant donné que les fichiers d'en-tête peuvent inclure d'autres fichiers d'en-tête, il est possible de se retrouver dans la situation où un fichier d'en-tête est inclus plusieurs fois.

Nous faisons donc des directives de préprocesseur pour éviter cela. Mais je ne suis pas sûr - pourquoi le compilateur ne peut-il pas juste ... ne pas importer deux fois la même chose?

Étant donné que les protections d'en-tête sont facultatives (mais apparemment une bonne pratique), cela me fait presque penser qu'il existe des scénarios lorsque vous souhaitez importer quelque chose deux fois. Bien que je ne puisse pas penser à un tel scénario du tout. Des idées?

Oméga
la source
Sur le compilateur MS, il y a #pragma oncequi indique au compilateur de n'inclure ce fichier qu'une seule fois.
CodesInChaos

Réponses:

27

Ils peuvent, comme le montrent les nouvelles langues qui le font.

Mais une décision de conception a été prise il y a toutes ces années (lorsque le compilateur C était à plusieurs étapes indépendantes) et maintenant pour maintenir la compatibilité, le pré-processeur doit agir d'une certaine manière pour s'assurer que l'ancien code compile comme prévu.

Comme C ++ hérite de la façon dont il traite les fichiers d'en-tête de C, il a conservé les mêmes techniques. Nous soutenons une ancienne décision de conception. Mais changer la façon dont cela fonctionne est trop risqué, beaucoup de code pourrait potentiellement casser. Alors maintenant, nous devons enseigner aux nouveaux utilisateurs de la langue comment utiliser les gardes.

Il y a quelques astuces avec les fichiers d'en-tête si vous les incluez délibérément plusieurs fois (cela fournit en fait une fonctionnalité utile). Bien que si nous avons repensé le paradigme à partir de zéro, nous pourrions en faire la manière non par défaut d'inclure des fichiers.

Martin York
la source
7

Ce ne serait pas aussi expressif autrement, étant donné qu'ils ont choisi de maintenir la compatibilité avec C et donc de continuer avec un préprocesseur plutôt qu'un système d'emballage traditionnel.

Une chose qui me vient à l'esprit est que j'avais un projet qui était une API. J'avais deux fichiers d'en-tête x86lib.het x86lib_internal.h. Parce que interne était énorme, j'ai séparé les bits "publics" en x86lib.h afin que les utilisateurs n'aient pas à réserver du temps supplémentaire pour la compilation.

Cela a cependant introduit un drôle de problème avec les dépendances, donc j'ai fini par avoir un flux qui ressemblait à ceci dans x86lib_internal

  1. Définir le préprocesseur INTERNE définir
  2. Inclure x86lib.h (qui était intelligent pour agir d'une certaine manière lorsque interne était défini)
  3. Faites quelques trucs et introduisez certaines choses utilisées dans x86lib.h
  4. Définir AFTER préprocesseur définir
  5. Incluez à nouveau x86lib.h (cette fois, il ignorerait tout sauf une partie AFTER séparée qui dépendait des éléments de x86lib_internal

Je ne dirais pas que c'était la meilleure façon de procéder, mais cela a atteint ce que je voulais.

Earlz
la source
0

Une des difficultés de l'exclusion automatique des en-têtes en double est que la norme C est relativement silencieuse sur la signification des noms de fichiers. Par exemple, supposons que le fichier principal en cours de compilation contient des directives #include "f1.h"et #include "f2.h", et que les fichiers trouvés pour ces directives contiennent tous les deux #include "f3.h". Si f1.het se f2.htrouvent dans des répertoires différents, mais ont été trouvés en recherchant les chemins d'inclusion, il ne serait pas clair que les #includedirectives dans ces fichiers étaient destinées à charger le même f3.hfichier ou des fichiers différents.

Les choses empirent encore si l'on ajoute les possibilités d'inclure des fichiers incluant des chemins relatifs. Dans certains cas, lorsque les fichiers d'en-tête utilisent des chemins relatifs pour les directives d'inclusion imbriquées et que l'on souhaite éviter de modifier les fichiers d'en-tête fournis, il peut être nécessaire de dupliquer un fichier d'en-tête à plusieurs endroits dans la structure de répertoires d'un projet. Même s'il existe plusieurs copies physiques de ce fichier d'en-tête, elles doivent être considérées sémantiquement comme s'il s'agissait d'un seul fichier.

Si la #pragma oncedirective permettait à un identificateur de suivre once, avec la sémantique que le compilateur devrait ignorer le fichier si l'identifiant correspond à celui d'une #pragma oncedirective rencontrée précédemment , alors la sémantique serait sans ambiguïté; un compilateur qui pourrait dire qu'une #includedirective chargerait le même #pragma oncefichier balisé qu'un précédent, il pourrait gagner un peu de temps en sautant le fichier sans l'ouvrir à nouveau, mais une telle détection ne serait pas sémantiquement importante car le fichier serait ignoré si ou non le nom de fichier a été reconnu comme une correspondance. Cependant, je ne connais aucun compilateur fonctionnant de cette façon. Demander à un compilateur d'observer si un fichier correspond au modèle #ifndef someIdentifier / #define someIdentifier / #endif [for that ifndef] / nothing followinget de traiter une telle chose comme équivalente à ce qui précède #pragma once someIdentifiersisomeIdentifier reste défini, est essentiellement aussi bon.

supercat
la source