J'ai eu une interview récemment et une question a été posée: quelle est l'utilisation de extern "C"
Dans le code C++? J'ai répondu qu'il s'agissait d'utiliser des fonctions C dans du code C++, car C n'utilisait pas de gestion des noms. On m'a demandé pourquoi C n'utilisait pas le pseudonyme et à vrai dire, je ne pouvais pas répondre.
Je comprends que lorsque le compilateur C++ compile des fonctions, il donne un nom spécial à la fonction principalement parce que nous pouvons avoir des fonctions surchargées du même nom en C++ qui doivent être résolues au moment de la compilation. En C, le nom de la fonction restera le même, ou peut-être avec un _ avant.
Ma question est la suivante: qu'est-ce qui ne va pas si le compilateur C++ peut également modifier les fonctions C? J'aurais supposé que peu importe les noms que le compilateur leur donne. Nous appelons des fonctions de la même manière en C et C++.
C'était en quelque sorte une réponse ci-dessus, mais je vais essayer de mettre les choses en contexte.
D'abord, C est arrivé en premier. En tant que tel, ce que C fait est, en quelque sorte, le "défaut". Cela ne modifie pas les noms parce que ce n'est tout simplement pas le cas. Un nom de fonction est un nom de fonction. Un global est un global, etc.
Puis C++ est arrivé. C++ voulait pouvoir utiliser le même éditeur de liens que C et pouvoir se lier avec du code écrit en C. Mais C++ ne pouvait pas laisser le "brouillage" C (ou le manque de) en l'état. Découvrez l'exemple suivant:
int function(int a);
int function();
En C++, ce sont des fonctions distinctes, avec des corps distincts. Si aucun d'entre eux n'est mutilé, les deux s'appelleront "fonction" (ou "_function"), et l'éditeur de liens se plaindra de la redéfinition d'un symbole. La solution C++ consistait à décomposer les types d'arguments dans le nom de la fonction. Ainsi, l’un s’appelle _function_int
Et l’autre s’appelle _function_void
(Ce n’est pas un schéma de brassage) et la collision est évitée.
Maintenant nous nous retrouvons avec un problème. Si int function(int a)
a été défini dans un module C, et que nous prenons simplement son en-tête (c'est-à-dire sa déclaration) dans le code C++ et l'utiliser, le compilateur générera une instruction à l'éditeur de liens pour importer _function_int
. Lorsque la fonction a été définie, dans le module C, cela ne s'appelait pas cela. Cela s'appelait _function
. Cela provoquera une erreur de l'éditeur de liens.
Pour éviter cette erreur, lors de la déclaration de la fonction, nous disons au compilateur qu'il s'agit d'une fonction destinée à être liée à, ou compilée par un compilateur C:
extern "C" int function(int a);
Le compilateur C++ sait maintenant importer _function
Plutôt que _function_int
, Et tout va bien.
Ce n'est pas qu'ils "ne peuvent pas", ils ne sont pas, en général.
Si vous voulez appeler une fonction dans une bibliothèque C appelée foo(int x, const char *y)
, il est inutile de laisser votre compilateur C++ modifier cela en foo_I_cCP()
(ou autre chose, vous venez de créer un schéma de gestion sur place. ici) juste parce que c'est possible.
Ce nom ne résoudra pas, la fonction est en C et son nom ne dépend pas de sa liste de types d'arguments. Donc, le compilateur C++ doit le savoir et marquer cette fonction comme étant C pour éviter de faire du mal.
Rappelez-vous que la fonction C peut se trouver dans une bibliothèque dont vous ne possédez pas le code source, tout ce que vous avez est le binaire précompilé et l’en-tête. Donc, votre compilateur C++ ne peut pas faire "c'est sa propre chose", il ne peut pas changer ce qui est dans la bibliothèque après tout.
qu'y a-t-il de mal à permettre au compilateur C++ de modifier également les fonctions C?
Ils ne seraient plus des fonctions C.
Une fonction n'est pas simplement une signature et une définition; Le fonctionnement d’une fonction est en grande partie déterminé par des facteurs tels que la convention d’appel. L '"interface binaire d'application" spécifiée pour être utilisée sur votre plate-forme décrit la manière dont les systèmes se parlent. L'ABI C++ utilisé par votre système spécifie un schéma de gestion des noms, de sorte que les programmes de ce système sachent comment appeler des fonctions dans des bibliothèques, etc. (Lisez l'ABI C++ Itanium pour un excellent exemple. Vous verrez très vite pourquoi c'est nécessaire.)
Il en va de même pour l’ABI C sur votre système. Certaines ABI C ont en fait un schéma de gestion des noms (Visual Studio, par exemple). Il s’agit donc moins de "désactiver le pseudonyme" que de passer de l’ABI C++ à l’ABI C, pour certaines fonctions. Nous marquons les fonctions C comme étant des fonctions C, auxquelles C ABI (plutôt que C++ ABI) est pertinente. La déclaration doit correspondre à la définition (que ce soit dans le même projet ou dans une bibliothèque tierce), sinon la déclaration est inutile. Sans cela, votre système ne saura tout simplement pas comment localiser/appeler ces fonctions.
En ce qui concerne les raisons pour lesquelles les plateformes ne définissent pas les ABI C et C++ comme étant identiques et éliminent ce "problème" partiellement historique - les ABI C d'origine n'étaient pas suffisantes pour C++, qui comporte des espaces de noms, des classes et des surcharges d'opérateurs. dont le nom d’un symbole doit d’une manière ou d’une autre être représenté de manière conviviale par l’ordinateur - mais on pourrait également affirmer que faire en sorte que les programmes C se conforment maintenant au C++ est injuste pour la communauté C, ce qui obligerait à faire face à une situation beaucoup plus compliquée. ABI juste pour le bien de certaines personnes qui veulent l’interopérabilité.
MSVC en fait fait modifier les noms en C, bien que de manière simple. Il ajoute parfois @4
ou un autre petit nombre. Cela concerne les conventions d’appel et la nécessité de nettoyer la pile.
Donc, la prémisse est juste imparfaite.
Il est très courant d'avoir des programmes partiellement écrits en C et partiellement écrits dans un autre langage (souvent le langage Assembly, mais parfois aussi Pascal, Fortran ou autre chose). Il est également courant que les programmes contiennent différents composants écrits par différentes personnes qui n'ont peut-être pas le code source pour tout.
Sur la plupart des plates-formes, il existe une spécification, souvent appelée ABI [Application Binary Interface], qui décrit ce qu'un compilateur doit faire pour produire une fonction avec un nom particulier qui accepte les arguments de certains types particuliers et renvoie une valeur d'un type particulier. Dans certains cas, une ABI peut définir plus d’une "convention d’appel"; les compilateurs de tels systèmes fournissent souvent un moyen d'indiquer quelle convention d'appel doit être utilisée pour une fonction particulière. Par exemple, sur le Macintosh, la plupart des routines Toolbox utilisent la convention d'appel Pascal. Le prototype de quelque chose comme "LineTo" serait donc quelque chose comme:
/* Note that there are no underscores before the "Pascal" keyword because
the Toolbox was written in the early 1980s, before the Standard and its
underscore convention were published */
Pascal void LineTo(short x, short y);
Si tout le code d'un projet a été compilé à l'aide du même compilateur, le nom du compilateur exporté pour chaque fonction importera peu, mais dans de nombreux cas, il sera nécessaire que le code C appelle des fonctions compilées à l'aide d'autres outils. ne peut pas être recompilé avec le compilateur actuel [et peut même ne pas être en C]. Pouvoir définir le nom de l'éditeur de liens est donc essentiel à l'utilisation de telles fonctions.
J'ajouterai une autre réponse pour aborder certaines des discussions tangentielles qui ont eu lieu.
C ABI (interface binaire d’application) appelait à l’origine pour passer les arguments sur la pile dans l’ordre inverse (c’est-à-dire - poussés de droite à gauche), l’appelant libérant également la mémoire de la pile. ABI moderne utilise en fait des registres pour passer des arguments, mais bon nombre des considérations néfastes reviennent à l'argument original de la pile.
Le Pascal ABI original, en revanche, poussait les arguments de gauche à droite et l'appelé devait les faire apparaître. L'original C ABI est supérieur à l'original Pascal ABI sur deux points importants. L'argument Ordre push signifie que le décalage de pile du premier argument est toujours connu, permettant ainsi aux fonctions dont le nombre d'arguments est inconnu, les arguments précédents contrôlant le nombre d'arguments existants (ala printf
).
La deuxième façon dont l’ABI C est supérieure est le comportement dans le cas où l’appelant et l’appelé ne s’entendent pas sur le nombre d’arguments. Dans le cas C, tant que vous n'accédez pas aux arguments après le dernier, rien de grave ne se produit. En Pascal, le nombre incorrect d'arguments est extrait de la pile et toute la pile est corrompue.
L'ABI Windows 3.1 d'origine était basé sur Pascal. En tant que tel, il utilisait Pascal ABI (arguments de gauche à droite, appels appelés). Etant donné que toute discordance dans le nombre d'arguments pourrait conduire à une corruption de pile, un système de mutilation a été créé. Chaque nom de fonction a été mutilé avec un nombre indiquant la taille, en octets, de ses arguments. Donc, sur une machine 16 bits, la fonction suivante (syntaxe C):
int function(int a)
A été mutilé à function@2
, car int
a une largeur de deux octets. Cela a été fait de sorte que si la déclaration et la définition ne concordent pas, l'éditeur de liens ne parvient pas à trouver la fonction plutôt que de corrompre la pile au moment de l'exécution. Inversement, si le programme est en liaison, vous pouvez être sûr que le nombre d'octets correct est extrait de la pile à la fin de l'appel.
Windows 32 bits et ultérieur utilisent plutôt le stdcall
ABI. Il est similaire à l’ABI Pascal, sauf que l’ordre Push est comme en C, de droite à gauche. Comme Pascal ABI, le nom mangling modifie la taille de l'octet d'argument dans le nom de la fonction pour éviter la corruption de pile.
Contrairement aux affirmations faites ailleurs, C ABI ne modifie pas les noms de fonction, même sous Visual Studio. Inversement, les fonctions de gestion associées à la spécification stdcall
ABI ne sont pas propres à VS. GCC prend également en charge cette ABI, même lors de la compilation pour Linux. Ceci est largement utilisé par Wine , qui utilise son propre chargeur pour permettre la liaison à l'exécution de fichiers binaires compilés Linux avec les DLL compilées Windows.
Les compilateurs C++ utilisent la gestion par nom afin de permettre des noms de symbole uniques pour les fonctions surchargées dont la signature serait autrement identique. Fondamentalement, il code également les types d'arguments, ce qui permet un polymorphisme au niveau fonction.
C n’a pas besoin de cela car il ne permet pas la surcharge de fonctions.
Notez que la gestion de nom est une (mais certainement pas la seule!) Raison pour laquelle on ne peut pas compter sur un 'ABI C++'.
C++ veut pouvoir interagir avec le code C qui le lie ou qui le lie.
C attend des noms de fonction non altérés par le nom.
Si C++ le modifiait, il ne trouverait pas les fonctions exportées non altérées de C ou C ne retrouverait pas les fonctions exportées par C++. L'éditeur de liens C doit obtenir le nom auquel il s'attend lui-même, car il ne sait pas qu'il provient ou passe en C++.
La gestion des noms de fonctions et de variables en C permettrait de vérifier leurs types au moment du lien. Actuellement, toutes les implémentations (?) C vous permettent de définir une variable dans un fichier et de l'appeler comme une fonction dans un autre. Ou vous pouvez déclarer une fonction avec une signature incorrecte (par exemple, void fopen(double)
), puis l'appeler.
J'ai proposé n schéma pour le couplage de variables C et de fonctions en fonction de la sécurité du type par l'utilisation du brassage en 1991. Le schéma n'a jamais été adopté, car, comme d'autres l'ont noté ici, cela détruirait la compatibilité en amont .