web-dev-qa-db-fra.com

Quel est l'effet de extern "C" en C ++?

Que fait exactement le fait de placer extern "C" dans du code C++?

Par exemple:

extern "C" {
   void foo();
}
1452
Litherum

extern "C" crée un nom de fonction en C++ avec une liaison "C" (le compilateur ne modifie pas le nom) de sorte que le code C du client puisse créer un lien vers (utiliser) votre fonction en utilisant un fichier d'en-tête compatible "C" déclaration de votre fonction. La définition de votre fonction est contenue dans un format binaire (compilé par votre compilateur C++) auquel l’éditeur de liens "C" du client se liera ensuite en utilisant le nom "C".

Étant donné que C++ surcharge les noms de fonctions et pas C, le compilateur C++ ne peut pas simplement utiliser le nom de la fonction comme identifiant unique vers lequel se lier, il modifie 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 a une liaison externe "C" en C++, le compilateur C++ n'ajoute pas d'informations de type argument/paramètre au nom utilisé. lien.

Pour votre information, vous pouvez spécifier explicitement le lien "C" dans chaque déclaration/définition ou utiliser un bloc pour regrouper une séquence de déclarations/définitions afin d’avoir un certain lien:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

Si vous vous souciez des aspects techniques, ils sont énumérés à la section 7.5 de la norme C++ 03. Voici un bref résumé (mettant l’accent sur extern "C"):

  • extern "C" est une spécification de liaison
  • Chaque compilateur est requis pour fournir un lien "C"
  • une spécification de liaison ne doit apparaître que dans la portée de l'espace de noms
  • tous les types de fonction, noms de fonction et noms de variable ont un lien de langueVoir le commentaire de Richard: Seuls les noms de fonction et les noms de variable avec un lien externe ont un lien de langue
  • deux types de fonctions avec des liens linguistiques distincts sont des types distincts même s'ils sont par ailleurs identiques
  • spécifications de liaison imbriquées, la partie interne détermine la liaison finale
  • extern "C" est ignoré pour les membres de la classe
  • au plus une fonction portant un nom particulier peut avoir un lien "C" (quel que soit l’espace de nom)
  • extern "C" force une fonction à avoir une liaison externe (ne peut pas la rendre statique)Voir le commentaire de Richard: 'statique' dans 'extern "C"' est valide; une entité ainsi déclarée a un lien interne et n'a donc pas de lien linguistique
  • La liaison de C++ à des objets définis dans d'autres langages et à des objets définis en C++ à partir d'autres langages est définie par la mise en œuvre et dépend du langage. Un tel lien ne peut être réalisé que lorsque les stratégies de disposition des objets de deux implémentations linguistiques sont suffisamment similaires
1419
Faisal Vali

Je voulais juste ajouter un peu d'information, car je ne l'ai pas encore vu posté.

Vous verrez très souvent le code dans les en-têtes C comme ceci:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Cela 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 aussi toujours l'utiliser avec votre code C hérité, où la macro est PAS définie, de sorte qu'il ne verra pas la construction uniquement C++.

Bien que, j'ai aussi vu le 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 le meilleur, mais j'ai vu les deux.

289
UncaAlby

Décompiler un binaire généré par g++ pour voir ce qu'il 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ésassemblez 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 étaient stockés dans des symboles portant 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 étaient et non mutilés:

  • défini
  • déclaré mais non défini (Ndx = UND), à fournir au moment de la liaison ou de l'exécution à partir d'un autre fichier objet

Vous aurez donc besoin de extern "C" à la fois pour appeler:

  • C à partir de C++: indiquez à g++ de s'attendre à ce que des symboles non mélangés soient produits par gcc
  • C++ à partir de C: indiquez à g++ de générer des symboles non mélangés pour gcc à utiliser

Choses qui ne fonctionnent pas dans extern C

Il devient évident que toute fonctionnalité C++ nécessitant une modification de nom ne fonctionnera pas dans 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) { }
}

C minimal exécutable à partir de l'exemple C++

Par souci d'exhaustivité et pour les débutants, voir aussi: Comment utiliser des fichiers source C dans un projet C++?

Appeler C depuis C++ est assez facile: chaque fonction C n'a qu'un seul symbole possible non mutilé, aucun travail supplémentaire n'est donc nécessaire.

main.cpp

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

c.h

#ifndef C_H
#define C_H

/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

#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()'

parce que g++ s'attend à trouver un f mutilé, que gcc n'a pas produit.

Exemple sur GitHub .

Exécution minimale de C++ à partir de l'exemple C

L'appel de C++ à partir de C est un peu plus difficile: nous devons créer manuellement des versions non mutilées de chaque fonction à exposer.

Nous illustrons ici comment exposer les surcharges de fonctions C++ en 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", il échoue avec:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

parce que g++ a généré des symboles mutilés que gcc ne peut pas trouver.

Exemple sur GitHub .

Testé dans Ubuntu 18.04.

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 identique au nom de la fonction. Cela est possible car en C, deux fonctions non statiques ne peuvent avoir le même nom.

Parce que C++ autorise la surcharge et a de nombreuses fonctionnalités que le C, à la différence des classes, des fonctions membres et des spécifications d'exception, il n'est pas possible d'utiliser simplement le nom de la fonction en tant que nom de symbole. Pour résoudre ce problème, C++ utilise ce que l'on appelle le nom mangling, 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 étrange, traitée uniquement par le compilateur et l'éditeur de liens.

Donc, si vous spécifiez une fonction comme étant extern C, le compilateur n'effectue pas de changement de nom avec elle et il est possible d'y accéder directement en utilisant le nom de son symbole comme nom de la fonction.

Ceci est pratique lorsque vous utilisez dlsym() et dlopen() pour appeler de telles fonctions.

198
sud03r

C++ modifie les noms de fonction pour créer un langage orienté objet à partir d'un langage procédural

La plupart des langages de programmation ne sont pas construits sur les langages de programmation existants. C++ est construit sur le C, et est en outre un langage de programmation orienté objet construit à partir d'un langage de programmation procédural. C'est pourquoi il existe des expressions C++ comme extern "C" qui fournissent une compatibilité ascendante 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;
}

Un compilateur C 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 int a vs char a).

gcc -o printMe printMe.c && ./printMe;
1 erreur. PrintMe est défini plusieurs fois.

Un compilateur C++ compilera l'exemple ci-dessus. Peu importe que printMe soit défini deux fois.

g ++ -o printMe printMe.c && ./printMe;

En effet, un compilateur C++ renomme implicitement ( mangles ) des fonctions en fonction de leurs paramètres. En C, cette fonctionnalité n'était pas supportée. Cependant, lorsque C++ était construit sur C, le langage était 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 des méthodes ( méthode ) en fonction de différents paramètres.

extern "C" dit "ne modifiez pas les noms de fonctions C"

Cependant, imaginons que nous ayons un fichier C hérité nommé "parent.c" qui includes les noms de fonctions d’autres fichiers C hérités, "parent.h", "child.h", etc. Si l’ancien héritage "parent.c "Le fichier est exécuté via un compilateur C++, puis les noms de fonction seront modifiés et ils ne correspondront plus aux noms de fonction spécifiés dans" parent.h "," enfant.h ", etc. - de sorte que les noms de fonction dans ces fichiers externes aurait également besoin d'être mutilé. La gestion des noms de fonction dans un programme C complexe, ceux qui comportent de nombreuses dépendances, peut conduire à une rupture du code. il peut donc être pratique de fournir un mot clé qui indique au compilateur C++ de ne pas modifier un nom de fonction.

Le mot clé extern "C" indique à un compilateur C++ de ne pas modifier (renommer) les noms de fonctions C. Exemple d'utilisation: extern "C" void printMe(int a);

40
tfmontague

Cela change le lien d'une fonction de telle manière que la fonction puisse être appelée à partir de C. En pratique, cela signifie que le nom de la fonction n'est pas mutilé .

27
Employed Russian

Aucun en-tête C ne peut être rendu compatible avec C++ en enveloppant simplement extern "C". Lorsque les identifiants d'un en-tête C sont en conflit avec des mots-clés C++, le compilateur C++ s'en plaint.

Par exemple, j'ai vu le code suivant échouer dans un g ++:

extern "C" {
struct method {
    int virtual;
};
}

Kinda a du sens, mais il faut en tenir compte lors du portage du code C en C++.

25
Sander Mertens

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 au cours de la phase de liaison.

19
Mark Rushakoff

extern "C" est destiné à être reconnu par un compilateur C++ et à informer le compilateur que la fonction indiquée est (ou doit être) compilée dans un style C. Ainsi, lors de la liaison, le lien vers la version correcte de la fonction à partir de C.

12
Flami

J'ai utilisé 'extern "C"' auparavant pour les fichiers dll (bibliothèque de liens dynamiques) afin de rendre etc. la fonction main () "exportable" afin qu'elle puisse être utilisée ultérieurement dans un autre exécutable à partir 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);
}
6
SturmCoder

extern "C" est une spécification de liaison qui est utilisée pour appeler les fonctions C dans 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)
{
    //
    //
}
5
Yogeesh H T

Cette réponse est pour les impatients/ont des délais à respecter, seule une partie/simple explication est ci-dessous:

  • en C++, vous pouvez avoir le même nom dans la classe via une surcharge (par exemple, étant donné qu'ils sont tous identiques, ne peuvent pas être exportés tels quels à partir d'une dll, etc.), la solution à ces problèmes est qu'ils sont convertis en chaînes différentes (appelées symboles ), symboles comptes le nom de la fonction, ainsi que les arguments, de sorte que chacune de ces fonctions, même avec le même nom, puisse être identifiée de manière unique (également appelée mangle de nom)
  • en C, vous n’avez pas de surcharge, le nom de la fonction est unique (donc, une chaîne distincte permettant d’identifier le nom de fonction de manière unique n’est pas obligatoire, le symbole est donc le nom de la fonction elle-même)

Alors
en C++, avec des noms uniques, identifiant chaque fonction
en C, même sans changement de nom, identifie de manière unique chaque fonction

Pour changer le comportement de C++, c’est-à-dire spécifier que le nom mangling ne devrait pas se produire pour une fonction particulière, vous pouvez utiliser extern "C" avant le nom de la fonction, pour quelle que soit la raison, telle que l'exportation d'une fonction avec un nom spécifique depuis une dll, à utiliser par ses clients.

Lisez les autres réponses pour des réponses plus détaillées/correctes.

2

Lorsque vous mélangez C et C++ (c'est-à-dire, une. Appelant une fonction C à partir de C++; et b. Appelant une fonction C++ à partir de C), la modification du nom C++ pose des problèmes de liaison. Techniquement, ce problème ne se produit que 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 nom malchangé en C++.

1
Trombe