Pourquoi ne devrions-nous pas inclure les fichiers sources en C

Pourquoi n’est-ce pas une bonne pratique d’inclure des fichiers source dans d’autres fichiers source? Le meilleur moyen consiste à inclure des fichiers d’en-tête à la place. Quels sont les avantages d’une telle approche et quels sont les inconvénients de l’inverse? Excusez mon mauvais anglais.

    Pourquoi n’est-ce pas une bonne pratique d’inclure des fichiers source dans d’autres fichiers source?

    Les fichiers source contiennent des définitions. Celles-ci peuvent entraîner plusieurs erreurs de définition et ne doivent donc généralement pas être incluses dans d’autres fichiers source. Même si vous évitez les erreurs de définition multiple en ne compilant que les fichiers contenant un autre fichier source, le code peut devenir ingérable.

    Dans les fichiers d’en-tête, il vous suffit d’introduire quelques symboles pour le compilateur et d’informer leurs types. Cela vous permet de séparer l’interface avec l’implémentation.

    Par exemple:

    fichier ac

     int a = 42; ... 

    fichier bc

     /* Example of bad code */ #include "ac" ... 

    Lorsque vous comstackz ac et bc et que vous les liez, vous obtiendrez multiple definition erreur de l’éditeur de liens multiple definition .

    Si on envisage d’inclure plusieurs fichiers sources dans un seul fichier et de le comstackr, cela introduira beaucoup de pollution (macros, fonctions statiques, etc.), ce qui est difficile à gérer pour les lecteurs et les compilateurs.


    ps Quand je dis en général, je veux dire parfois que l’inclusion de code source peut être utile. Mais dans de tels cas, pour éviter les confusions aux lecteurs, je préférerais renommer le suffixe du fichier en un nom autre que .c , qui peut être .inc ou similaire.

    Tout fichier #include d est compilé comme si son texte remplaçait la directive #include correspondante par le préprocesseur. Bien que cela ne corresponde pas exactement, vous trouverez ici de plus amples informations à ce sujet. Notez la protection du préprocesseur.

    La vraie question est de savoir ce que vous ne devriez pas mettre dans un tel en-tête. Ce serait tout ce qui fait qu’un nom avec une liaison externe soit défini dans plusieurs unités / modules de compilation . Vous ne devez pas non plus placer d’objects ici, qui ne sont utilisés que dans un seul de ces modules et / ou doivent être cachés des autres modules.

    Cela inclurait les fonctions en général: l’en-tête ne fournit que la déclaration , la définition sera dans un seul module. Une exception sont inline fonctions en inline , qui doivent en réalité être définies dans l’en-tête.

    La plupart du temps, pour les structures de données, il en va de même pour les fonctions. Cependant, il peut y avoir des exceptions pour static structures static qui doivent être fournies par tous les modules. Une autre exception pourrait être les fichiers générés automatiquement, par exemple les tableaux qui ne sont utilisés que par un seul module. Ceux-ci doivent également être #include d, mais déclarés static . Normalement, on utiliserait une extension différente pour de tels fichiers, par exemple .inc au lieu de .h .

    Pour un pré-processeur, l’extension d’un fichier n’a vraiment aucune importance. Vous pouvez mettre du code dans un fichier avec une extension “JPG” et vous pouvez toujours # l’inclure sans erreur, à condition que le code soit légitime.

    L’une des raisons pour lesquelles, en règle générale, il est considéré comme une mauvaise pratique d’ #include fichiers avec une extension de fichier source provient d’une perspective de base construction / création. Imaginez que vous portiez un projet de grande envergure sur un nouveau système de compilation multiplate-forme (50 millions de lignes de code, par exemple).

    Vous devez maintenant spécifier quels fichiers doivent être construits en tant qu’unités de compilation distinctes (fichiers objects) à comstackr séparément et liées pour former le binary résultant. Si votre base de code a l’habitude d’utiliser le préprocesseur pour inclure des fichiers avec une extension de fichier source, vous n’avez alors aucune idée en regardant simplement les extensions de fichiers qui doivent être construites en tant qu’unités de compilation séparées et quels fichiers doivent en fait être simplement inclus. par le préprocesseur. Vous risquez alors de faire face à un spam d’erreurs en essayant simplement de créer tous les fichiers source comme des unités de compilation distinctes, comme le ferait une personne sensée, et devrez peut-être déboguer votre processus de construction à l’aide d’un peigne fin tout en inspectant tout votre code et en essayant de comprendre quel fichier est destiné à quoi.

    À un niveau supérieur, au-delà des extensions de fichier, si vous définissez réellement des éléments dans les fichiers source et les incluez avec le pré-processeur, vous risquez des définitions redondantes de l’éditeur de liens des mêmes symboles, ainsi que des erreurs délicates au moment de la compilation (et éventuellement de la compilation). De plus, cela peut donner lieu à une rupture générale de la pensée entre la séparation interface / déclaration (en-têtes) et implémentation / définition (sources).

    Il existe des exceptions, comme les unités Unity, qui optimisent le temps de construction et peuvent être acceptables avec des normes de codage ssortingctes et des avantages réels et mesurés, mais en général, inclure des fichiers source peut être très déroutant et indiquer que le Le développeur ne comprend pas vraiment le sharepoint séparer les déclarations des définitions ou la confusion que cela peut causer en essayant d’établir un système de construction.

    Comme mentionné précédemment, le principal argument contre l’inclusion de fichiers C dans les fichiers C est le risque élevé d’erreurs de définition multiples. Et comme il s’agit d’une technique très peu utilisée, elle entraîne des effets secondaires inattendus pour les responsables du code.

    Bien sûr, dans des cas très particuliers, inclure un fichier C peut être le moindre des deux maux. Par exemple, si vous souhaitez écrire des tests unitaires pour des fonctions c statiques, vous pouvez inclure le fichier C dans le fichier avec le test unitaire.

    voir aussi: Comment tester une fonction statique

    Une autre utilisation inhabituelle mais valable consiste à séparer les modèles de classe ou de fonction de leur définition (C ++): https://isocpp.org/wiki/faq/templates#separate-template-fn-defn-from-decl

    Les fichiers source c doivent avoir des définitions. Par exemple, si vous avez une fonction int add(int, int) qui ajoute deux nombres, sa définition ressemblerait à ceci:

     int add(int x, int y) { return x + y; } 

    un fichier d’en-tête contient un prototype qui aide le compilateur à appeler cette fonction lorsqu’elle est appelée dans votre code. Il indique au compilateur comment créer le cadre de stack pour la fonction, combien de parameters et leurs types, ainsi que le type de retour.

    Si vous incluez un fichier source contenant l’exemple de code ci-dessus, deux définitions de la fonction add() seront nécessaires, ce qui n’est pas possible.

    Au lieu de cela, vous ajoutez le prototype à un fichier d’en-tête, comme ceci

     int add(int x, int y); 

    puis incluez le fichier d’en-tête, de cette manière, il n’y aura qu’une seule définition pour la fonction add() .

    Vous vous demandez peut-être alors comment la fonction fonctionnera si je l’utilise dans un autre fichier source c sans fournir de définition.

    La réponse est que la définition de la fonction n’est requirejse que lorsque le compilateur lie tous les fichiers object au binary final.