Que fait exactement la mise extern "C"
en code C ++?
Par exemple:
extern "C" {
void foo();
}
foo()
fonction.
Que fait exactement la mise extern "C"
en code C ++?
Par exemple:
extern "C" {
void foo();
}
foo()
fonction.
Réponses:
extern "C" fait un nom de fonction en C ++ avoir un lien 'C' (le compilateur ne modifie pas le nom) afin que le code C client puisse se lier à (c'est-à-dire utiliser) votre fonction en utilisant un fichier d'en-tête compatible 'C' qui contient juste le déclaration de votre fonction. Votre définition de fonction est contenue dans un format binaire (qui a été compilé par votre compilateur C ++) que l'éditeur de liens «C» du client liera ensuite à l'aide du nom «C».
Étant donné que C ++ a une surcharge de noms de fonctions et que C ne le fait pas, le compilateur C ++ ne peut pas simplement utiliser le nom de la fonction comme identifiant unique pour se lier à, il modifie donc le nom en ajoutant des informations sur les arguments. Le compilateur AC n'a pas besoin de modifier le nom car vous ne pouvez pas surcharger les noms de fonction en C. Lorsque vous indiquez qu'une fonction possède une liaison externe "C" en C ++, le compilateur C ++ n'ajoute pas d'informations d'argument / type de paramètre au nom utilisé pour lien.
Juste pour que vous le sachiez, vous pouvez spécifier explicitement le lien "C" à chaque déclaration / définition individuelle ou utiliser un bloc pour regrouper une séquence de déclarations / définitions pour avoir un certain lien:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Si vous vous souciez des détails techniques, ils sont répertoriés dans la section 7.5 de la norme C ++ 03, voici un bref résumé (en mettant l'accent sur le "C" externe):
extern "C" { int i; }
c'est une définition. Ce n'est peut-être pas ce que vous vouliez, à côté de la non-définition de void g(char);
. Pour en faire une non-définition, vous auriez besoin extern "C" { extern int i; }
. D'un autre côté, la syntaxe à une déclaration sans accolades fait de la déclaration une non-définition: extern "C" int i;
est la même queextern "C" { extern int i; }
Je voulais juste ajouter un peu d'informations, car je ne les ai pas encore vues.
Vous verrez très souvent du code dans les en-têtes C comme ceci:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Ce que cela accomplit, c'est qu'il vous permet d'utiliser ce fichier d'en-tête C avec votre code C ++, car la macro "__cplusplus" sera définie. Mais vous pouvez également l' utiliser avec votre code C hérité, où la macro n'est PAS définie, de sorte qu'il ne verra pas la construction C ++ uniquement.
Cependant, j'ai également vu du code C ++ tel que:
extern "C" {
#include "legacy_C_header.h"
}
que j'imagine accomplit à peu près la même chose.
Je ne sais pas quel chemin est meilleur, mais j'ai vu les deux.
extern "C"
dans l'en-tête). Cela fonctionne très bien, a utilisé cette technique plusieurs fois.
extern "C"
avant ou après qu'il inclut l'en-tête. Au moment où il atteint le compilateur, ce n'est de toute façon qu'un long flux de texte prétraité.
g++
s'est trompée, pour aucune cible, à aucun moment au cours des 17 dernières années au moins. L'intérêt du premier exemple est que peu importe que vous utilisiez un compilateur C ou C ++, aucun changement de nom ne sera effectué pour les noms dans le extern "C"
bloc.
Décompiler un g++
binaire généré pour voir ce qui se passe
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Compilez et démontez la sortie ELF générée:
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
La sortie contient:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Interprétation
On voit ça:
ef
et eg
ont été stockés dans des symboles avec le même nom que dans le code
les autres symboles ont été mutilés. Démêlons-les:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Conclusion: les deux types de symboles suivants n'ont pas été mutilés:
Ndx = UND
), à fournir lors de la liaison ou de l'exécution à partir d'un autre fichier objetVous aurez donc besoin des extern "C"
deux lors de votre appel:
g++
d'attendre des symboles démêlés produits pargcc
g++
de générer des symboles démêlés gcc
à utiliserChoses qui ne fonctionnent pas en externe C
Il devient évident que toute fonctionnalité C ++ qui nécessite un changement de nom ne fonctionnera pas à l'intérieur extern C
:
extern "C" {
// Overloading.
// error: declaration of C function ‘void f(int)’ conflicts with
void f();
void f(int i);
// Templates.
// error: template with C linkage
template <class C> void f(C i) { }
}
Exemple C exécutable minimal à partir de C ++
Par souci d'exhaustivité et pour les nouveautés, voir aussi: Comment utiliser les fichiers source C dans un projet C ++?
L'appel de C à partir de C ++ est assez simple: chaque fonction C n'a qu'un seul symbole non mutilé possible, donc aucun travail supplémentaire n'est requis.
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
ch
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
cc
#include "c.h"
int f(void) { return 1; }
Courir:
g++ -c -o main.o -std=c++98 main.cpp
gcc -c -o c.o -std=c89 c.c
g++ -o main.out main.o c.o
./main.out
Sans extern "C"
le lien échoue avec:
main.cpp:6: undefined reference to `f()'
car g++
s'attend à trouver un mutilé f
, qui gcc
n'a pas produit.
Exemple de C ++ exécutable minimal à partir de C
Appeler C ++ à partir de C est un peu plus difficile: nous devons créer manuellement des versions non mutilées de chaque fonction que nous voulons exposer.
Nous illustrons ici comment exposer les surcharges de fonctions C ++ à C.
principal c
#include <assert.h>
#include "cpp.h"
int main(void) {
assert(f_int(1) == 2);
assert(f_float(1.0) == 3);
return 0;
}
cpp.h
#ifndef CPP_H
#define CPP_H
#ifdef __cplusplus
// C cannot see these overloaded prototypes, or else it would get confused.
int f(int i);
int f(float i);
extern "C" {
#endif
int f_int(int i);
int f_float(float i);
#ifdef __cplusplus
}
#endif
#endif
cpp.cpp
#include "cpp.h"
int f(int i) {
return i + 1;
}
int f(float i) {
return i + 2;
}
int f_int(int i) {
return f(i);
}
int f_float(float i) {
return f(i);
}
Courir:
gcc -c -o main.o -std=c89 -Wextra main.c
g++ -c -o cpp.o -std=c++98 cpp.cpp
g++ -o main.out main.o cpp.o
./main.out
Sans extern "C"
cela il échoue avec:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
car g++
généré des symboles mutilés qui gcc
ne peuvent pas trouver.
Testé dans Ubuntu 18.04.
extern "C" {
vous aide à appeler des fonctions C démêlées à partir de programmes C ++ , ainsi que des fonctions C ++ démêlées à partir de programmes C , que les autres réponses ne rendent pas si évidentes, et 2) parce que vous montrez des exemples distincts de chaque. Merci!
Dans chaque programme C ++, toutes les fonctions non statiques sont représentées dans le fichier binaire sous forme de symboles. Ces symboles sont des chaînes de texte spéciales qui identifient de manière unique une fonction dans le programme.
En C, le nom du symbole est le même que le nom de la fonction. Ceci est possible car en C aucune fonction non statique ne peut avoir le même nom.
Parce que C ++ permet la surcharge et possède de nombreuses fonctionnalités que C n'a pas - comme les classes, les fonctions membres, les spécifications d'exception - il n'est pas possible d'utiliser simplement le nom de la fonction comme nom de symbole. Pour résoudre cela, C ++ utilise ce que l'on appelle le mangling de nom, qui transforme le nom de la fonction et toutes les informations nécessaires (comme le nombre et la taille des arguments) en une chaîne d'aspect étrange traitée uniquement par le compilateur et l'éditeur de liens.
Donc, si vous spécifiez une fonction pour être un C externe, le compilateur n'effectue pas de changement de nom avec elle et il est directement accessible en utilisant son nom de symbole comme nom de fonction.
Cela est pratique lors de l'utilisation dlsym()
et dlopen()
de l'appel de ces fonctions.
La plupart des langages de programmation ne sont pas construits sur les langages de programmation existants. C ++ est construit au-dessus de C, et en plus c'est un langage de programmation orienté objet construit à partir d'un langage de programmation procédural, et pour cette raison il existe des expressions C ++ comme extern "C"
qui offrent une compatibilité descendante avec C.
Regardons l'exemple suivant:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
void printMe(int a) {
printf("int: %i\n", a);
}
void printMe(char a) {
printf("char: %c\n", a);
}
int main() {
printMe("a");
printMe(1);
return 0;
}
Le compilateur AC ne compilera pas l'exemple ci-dessus, car la même fonction printMe
est définie deux fois (même s'ils ont des paramètres différents par int a
rapport à char a
).
gcc -o printMe printMe.c && ./printMe;
1 erreur. PrintMe est défini plusieurs fois.
Un compilateur C ++ compilera l'exemple ci-dessus. Il ne se soucie pas de ce qui printMe
est défini deux fois.
g ++ -o printMe printMe.c && ./printMe;
En effet, un compilateur C ++ renomme implicitement ( mangles ) les fonctions en fonction de leurs paramètres. En C, cette fonctionnalité n'était pas prise en charge. Cependant, lorsque C ++ a été construit sur C, le langage a été conçu pour être orienté objet et devait prendre en charge la possibilité de créer différentes classes avec des méthodes (fonctions) du même nom, et de remplacer les méthodes (substitution de méthode ) basées sur différents paramètres.
extern "C"
dit "ne pas modifier les noms de fonction C"Cependant, imaginez que nous avons un fichier C hérité nommé "parent.c" qui include
s noms de fonction à partir d'autres fichiers C hérités, "parent.h", "child.h", etc. Si le fichier "parent.c" hérité est exécuté via un compilateur C ++, les noms de fonction seront modifiés et ils ne correspondront plus aux noms de fonction spécifiés dans "parent.h", "child.h", etc. - donc les noms de fonction dans ces fichiers externes devront également être mutilé. La gestion des noms de fonction dans un programme C complexe, ceux avec beaucoup de dépendances, peut conduire à un code cassé; il peut donc être pratique de fournir un mot-clé qui peut indiquer au compilateur C ++ de ne pas modifier un nom de fonction.
Le extern "C"
mot clé indique à un compilateur C ++ de ne pas modifier (renommer) les noms de fonction C.
Par exemple:
extern "C" void printMe(int a);
extern "C"
si nous avons juste un dll
fichier? Je veux dire si nous n'avons pas de fichier d'en-tête et avons juste un fichier source (juste des implémentations) et l'utilisation de sa fonction via le pointeur de fonction. dans cet état, nous venons d'utiliser des fonctions (quel que soit son nom).
Aucun en-tête C ne peut être rendu compatible avec C ++ en enveloppant simplement le "C" externe. Lorsque des identificateurs dans un en-tête C entrent en conflit avec des mots clés C ++, le compilateur C ++ s'en plaindra.
Par exemple, j'ai vu le code suivant échouer dans un g ++:
extern "C" {
struct method {
int virtual;
};
}
C'est un peu logique, mais c'est quelque chose à garder à l'esprit lors du portage de code C en C ++.
extern "C"
signifie utiliser la liaison C, comme décrit par d'autres réponses. Cela ne signifie pas «compiler le contenu en C» ou quoi que ce soit. int virtual;
n'est pas valide en C ++ et la spécification d'une liaison différente ne change rien à cela.
Il modifie le lien d'une fonction de telle sorte que la fonction peut être appelée à partir de C. En pratique, cela signifie que le nom de la fonction n'est pas modifié .
undname
.
Il informe le compilateur C ++ de rechercher les noms de ces fonctions dans un style C lors de la liaison, car les noms des fonctions compilées en C et C ++ sont différents pendant l'étape de liaison.
J'ai utilisé 'extern "C"' avant pour les fichiers dll (bibliothèque de liens dynamiques) pour rendre la fonction etc. main () "exportable" afin qu'elle puisse être utilisée plus tard dans un autre exécutable de dll. Peut-être qu'un exemple de l'endroit où je l'utilisais peut être utile.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
et ne __declspec(dllexport)
sont pas liés. Le premier contrôle la décoration des symboles, le second est responsable de la création d'une entrée d'exportation. Vous pouvez également exporter un symbole en utilisant la décoration de nom C ++. En plus de manquer complètement le point de cette question, il y a aussi d'autres erreurs dans l'exemple de code. D'une part, main
exporté à partir de votre DLL ne déclare pas de valeur de retour. Ou appeler convention, d'ailleurs. Lors de l'importation, vous attribuez une convention d'appel aléatoire ( WINAPI
) et utilisez le mauvais symbole pour les générations 32 bits (devrait être _main
ou _main@0
). Désolé, -1.
void*
, mais votre implémentation ne renvoie rien. Ça va très bien voler ...
extern "C"
est une spécification de liaison utilisée pour appeler des fonctions C dans les fichiers source Cpp . Nous pouvons appeler des fonctions C, écrire des variables et inclure des en-têtes . La fonction est déclarée dans l'entité externe et elle est définie à l'extérieur. La syntaxe est
Type 1:
extern "language" function-prototype
Type 2:
extern "language"
{
function-prototype
};
par exemple:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Cette réponse est pour les impatients / ont des délais à respecter, seule une partie / explication simple est ci-dessous:
Donc,
en C ++, avec le nom qui identifie de façon unique les identités de chaque fonction
en C, même sans le nom qui identifie de manière unique chaque fonction
Pour modifier le comportement de C ++, c'est-à-dire pour spécifier que le changement de nom ne doit pas se produire pour une fonction particulière, vous pouvez utiliser extern "C" avant le nom de la fonction, pour une raison quelconque, comme exporter une fonction avec un nom spécifique à partir d'une DLL , à l'usage de ses clients.
Lisez les autres réponses, pour des réponses plus détaillées / plus correctes.
Lors du mélange de C et C ++ (c'est-à-dire, a. Appelant la fonction C à partir de C ++; et b. Appelant la fonction C ++ à partir de C), le changement de nom C ++ provoque des problèmes de liaison. Techniquement parlant, ce problème se produit uniquement lorsque les fonctions appelées ont déjà été compilées en binaire (très probablement, un fichier de bibliothèque * .a) à l'aide du compilateur correspondant.
Nous devons donc utiliser extern "C" pour désactiver le changement de nom en C ++.
Sans entrer en conflit avec d'autres bonnes réponses, j'ajouterai un peu de mon exemple.
Ce que fait exactement le compilateur C ++ : il modifie les noms dans le processus de compilation, d'où la nécessité de dire au compilateur de traiter l' C
implémentation spécialement.
Lorsque nous créons et ajoutons des classes C ++ extern "C"
, nous disons à notre compilateur C ++ que nous utilisons la convention d'appel C.
Raison (nous appelons l'implémentation C depuis C ++): soit nous voulons appeler la fonction C depuis C ++, soit nous appelons la fonction C ++ depuis C (les classes C ++ ... etc ne fonctionnent pas en C).
Une fonction void f () compilée par un compilateur C et une fonction du même nom void f () compilée par un compilateur C ++ ne sont pas la même fonction. Si vous avez écrit cette fonction en C, puis que vous avez essayé de l'appeler à partir de C ++, l'éditeur de liens rechercherait la fonction C ++ et ne trouverait pas la fonction C.
extern "C" indique au compilateur C ++ que vous avez une fonction qui a été compilée par le compilateur C. Une fois que vous lui avez dit qu'il a été compilé par le compilateur C, le compilateur C ++ saura comment l'appeler correctement.
Il permet également au compilateur C ++ de compiler une fonction C ++ de telle manière que le compilateur C puisse l'appeler. Cette fonction serait officiellement une fonction C, mais puisqu'elle est compilée par le compilateur C ++, elle peut utiliser toutes les fonctionnalités C ++ et possède tous les mots clés C ++.
extern "C"
fonction - et (sous réserve de certaines contraintes) il sera appelable par du code compilé par un compilateur C.