web-dev-qa-db-fra.com

Pourquoi pouvez-vous avoir la définition de méthode dans le fichier d'en-tête en C ++ alors qu'en C vous ne pouvez pas?

En C, vous ne pouvez pas avoir la définition/implémentation de fonction dans le fichier d'en-tête. Cependant, en C++, vous pouvez avoir une implémentation complète de la méthode dans le fichier d'en-tête. Pourquoi le comportement est-il différent?

25
Joshua Partogi

En C, si vous définissez une fonction dans un fichier d'en-tête, cette fonction apparaîtra dans chaque module compilé qui inclut ce fichier d'en-tête et un symbole public sera exporté pour la fonction. Donc, si la fonction additup est définie dans header.h, et foo.c et bar.c incluent tous les deux header.h, alors foo.o et bar.o incluront tous les deux des copies d'additup.

Lorsque vous allez lier ces deux fichiers objets ensemble, l'éditeur de liens verra que le symbole additup est défini plusieurs fois et ne le permettra pas.

Si vous déclarez que la fonction est statique, aucun symbole ne sera exporté. Les fichiers objets foo.o et bar.o contiendront tous les deux des copies distinctes du code de la fonction, et ils pourront les utiliser, mais l'éditeur de liens ne pourra voir aucune copie de la fonction, donc il ne se plaindra pas. Bien entendu, aucun autre module ne pourra non plus voir la fonction. Et votre programme sera gonflé de deux copies identiques de la même fonction.

Si vous déclarez uniquement la fonction dans le fichier d'en-tête, mais ne la définissez pas, puis la définissez dans un seul module, l'éditeur de liens verra une copie de la fonction, et chaque module de votre programme pourra la voir et utilise le. Et votre programme compilé ne contiendra qu'une seule copie de la fonction.

Donc, vous pouvez avez la définition de la fonction dans le fichier d'en-tête en C, c'est juste un mauvais style, une mauvaise forme et une mauvaise idée générale.

(Par "déclarer", je veux dire fournir un prototype de fonction sans corps; par "définir", je veux dire fournir le code réel du corps de fonction; c'est la terminologie C standard.)

29
David Conrad

C et C++ se comportent à peu près de la même manière à cet égard - vous pouvez avoir des fonctions inline dans les en-têtes. En C++, toute méthode dont le corps est à l'intérieur de la définition de classe est implicitement inline. Si vous voulez faire de même en C, déclarez les fonctions static inline.

30
Simon Richter

Le concept d'un fichier d'en-tête a besoin d'une petite explication:

Soit vous donnez un fichier sur la ligne de commande du compilateur, soit vous faites un '#include'. La plupart des compilateurs acceptent un fichier de commandes avec l'extension c, C, cpp, c ++, etc. comme fichier source. Cependant, ils incluent généralement une option de ligne de commande pour permettre également l'utilisation de toute extension arbitraire dans un fichier source.

Généralement, le fichier donné sur la ligne de commande est appelé "Source", et celui inclus est appelé "En-tête".

L'étape du préprocesseur les prend tous et fait tout apparaître comme un gros fichier unique au compilateur. Ce qui était dans l'en-tête ou dans la source n'est en fait pas pertinent à ce stade. Il y a généralement une option d'un compilateur qui peut afficher la sortie de cette étape.

Donc, pour chaque fichier qui a été donné sur la ligne de commande du compilateur, un énorme fichier est donné au compilateur. Cela peut avoir du code/des données qui occuperont de la mémoire et/ou créeront un symbole à référencer à partir d'autres fichiers. Maintenant, chacun d'eux va générer une image "objet". L'éditeur de liens peut donner un "symbole en double" si le même symbole se trouve dans plus de deux fichiers objets qui sont liés ensemble. C'est peut-être la raison; il n'est pas conseillé de mettre du code dans un fichier d'en-tête, ce qui peut créer des symboles dans le fichier objet.

Les 'inline' sont généralement en ligne .. mais lors du débogage, ils peuvent ne pas être en ligne. Alors pourquoi l'éditeur de liens ne donne-t-il pas d'erreurs définies de façon multiple? Simple ... Ce sont des symboles "faibles", et tant que toutes les données/code pour un symbole faible de tous les objets ont la même taille et le même contenu, le lien gardera une copie et supprimera la copie des autres objets. Ça marche.

7
vrdhn

Vous pouvez le faire en C99: les fonctions inline sont assurées d'être fournies ailleurs, donc si une fonction n'est pas insérée, sa définition est traduite dans une déclaration (c'est-à-dire que l'implémentation est rejetée). Et, bien sûr, vous pouvez utiliser static.

4
SK-logic

Citations standard C++

Le C++ 17 N4659 standard draft 10.1.6 "The inline specifier" dit que les méthodes sont implicitement en ligne:

4 Une fonction définie dans une définition de classe est une fonction en ligne.

puis plus loin, nous voyons que les méthodes en ligne peuvent non seulement être définies, mais doivent être définies sur toutes les unités de traduction:

6 Une fonction ou une variable en ligne doit être définie dans chaque unité de traduction dans laquelle elle est utilisée ou doit avoir exactement la même définition dans tous les cas (6.2).

Ceci est également explicitement mentionné dans une note au 12.2.1 "Fonctions membres":

1 Une fonction membre peut être définie (11.4) dans sa définition de classe, auquel cas il s'agit d'une fonction membre inline (10.1.6) [...]

3 [Remarque: Il peut y avoir au plus une définition d'une fonction membre non en ligne dans un programme. Il peut y avoir plus d'une définition de fonction de membre inline dans un programme. Voir 6.2 et 10.1.6. - note de fin]

Implémentation de GCC 8.3

main.cpp

struct MyClass {
    void myMethod() {}
};

int main() {
    MyClass().myMethod();
}

Compilez et visualisez les symboles:

g++ -c main.cpp
nm -C main.o

production:

                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
                 U __stack_chk_fail
0000000000000000 T main

alors nous voyons dans man nm que le symbole MyClass::myMethod est marqué comme faible sur les fichiers objets ELF, ce qui implique qu'il peut apparaître sur plusieurs fichiers objets:

"W" "w" Le symbole est un symbole faible qui n'a pas été spécifiquement marqué comme symbole d'objet faible. Lorsqu'un symbole défini faible est lié à un symbole défini normalement, le symbole défini normalement est utilisé sans erreur. Lorsqu'un symbole faible non défini est lié et que le symbole n'est pas défini, la valeur du symbole est déterminée sans erreur spécifique au système. Sur certains systèmes, les majuscules indiquent qu'une valeur par défaut a été spécifiée.