it-swarm-fr.com

Pourquoi avons-nous besoin d'extern "C" {#include <foo.h>} en C ++?

Pourquoi avons-nous besoin d'utiliser:

extern "C" {
#include <foo.h>
}

spécifiquement:

  • Quand devrions-nous l'utiliser?

  • Que se passe-t-il au niveau du compilateur/éditeur de liens qui nous oblige à l'utiliser?

  • Comment, en termes de compilation/mise en lien, cela résout-il les problèmes qui nous obligent à l’utiliser?

133
Landon

C et C++ sont superficiellement similaires, mais chacun compile dans un ensemble de code très différent. Lorsque vous incluez un fichier d'en-tête avec un compilateur C++, le compilateur attend du code C++. Si, toutefois, il s’agit d’un en-tête C, le compilateur s’attend à ce que les données contenues dans le fichier d’en-tête soient compilées dans un certain format, l’ABI C++ ou l’interface binaire d’application, de sorte que l’éditeur de liens se bloque. Cela est préférable à la transmission de données C++ à une fonction qui attend des données C.

(Pour entrer dans le vif du sujet, ABI de C++ modifie généralement le nom de ses fonctions/méthodes, appelant donc printf() sans marquer le prototype comme une fonction C, le C++ générera en fait l'appel de code _Zprintf, plus de la merde à la fin.)

Donc: utilisez extern "C" {...} lorsque vous incluez un en-tête c, c'est aussi simple que cela. Sinon, vous aurez une incompatibilité dans le code compilé et l'éditeur de liens s'étouffera. Cependant, pour la plupart des en-têtes, vous n’aurez même pas besoin de extern, car la plupart des en-têtes système C prendront déjà en compte le fait qu’ils pourraient être inclus dans le code C++ et déjà extern leur code.

121
duane

extern "C" détermine comment les symboles du fichier objet généré doivent être nommés. Si une fonction est déclarée sans extern "C", le nom du symbole dans le fichier objet utilisera le nom C++. Voici un exemple.

Compte tenu de test.C comme si:

void foo() { }

La compilation et la liste des symboles dans le fichier objet donne:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

La fonction foo s'appelle en fait "_Z3foov". Cette chaîne contient des informations de type pour le type de retour et les paramètres, entre autres. Si vous écrivez plutôt test.C comme ceci:

extern "C" {
    void foo() { }
}

Ensuite, compilez et regardez les symboles:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

Vous obtenez un lien en C. Le nom de la fonction "foo" dans le fichier objet est juste "foo", et il ne contient pas toutes les informations de type fantaisie qui proviennent de nom mangling.

Vous incluez généralement un en-tête dans extern "C" {} si le code qui l'accompagne a été compilé avec un compilateur C mais que vous essayez de l'appeler à partir de C++. En faisant cela, vous dites au compilateur que toutes les déclarations de l'en-tête utiliseront le lien C. Lorsque vous liez votre code, vos fichiers .o contiendront des références à "foo", et non "_Z3fooblah", ce qui correspond, espérons-le, à tout ce qui est dans la bibliothèque avec laquelle vous créez un lien.

La plupart des bibliothèques modernes mettront des gardes autour de ces en-têtes afin que les symboles soient déclarés avec le bon lien. par exemple. dans beaucoup d'en-têtes standard, vous trouverez:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Cela garantit que lorsque le code C++ inclut l'en-tête, les symboles de votre fichier objet correspondent à ceux de la bibliothèque C. Vous ne devriez avoir à mettre extern "C" {} autour de votre en-tête C que s'il est vieux et qu'il ne possède pas déjà ces gardes.

108
Todd Gamblin

En C++, vous pouvez avoir différentes entités qui partagent un nom. Par exemple, voici une liste de fonctions toutes nommées foo:

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

Afin de les différencier tous, le compilateur C++ créera des noms uniques pour chacun d’entre eux dans un processus appelé gestion des noms ou décoration. Les compilateurs C ne le font pas. De plus, chaque compilateur C++ peut faire cela d'une manière différente.

extern "C" indique au compilateur C++ de ne pas modifier le nom de code entre accolades. Cela vous permet d’appeler des fonctions C à partir de C++.

21
Trent

Cela a à voir avec la façon dont les différents compilateurs gèrent les noms. Un compilateur C++ modifie le nom d'un symbole exporté du fichier d'en-tête d'une manière complètement différente de celle d'un compilateur C. Ainsi, lorsque vous essayez de créer un lien, vous obtenez une erreur de l'éditeur de liens indiquant qu'il manque des symboles.

Pour résoudre ce problème, nous demandons au compilateur C++ de s'exécuter en mode "C", de sorte qu'il modifie les noms de la même manière que le compilateur C. Cela fait, les erreurs de l'éditeur de liens sont corrigées.

14
1800 INFORMATION

Quand devrions-nous l'utiliser?

Lorsque vous liez des bibliothèques C dans des fichiers objet C++

Que se passe-t-il au niveau du compilateur/éditeur de liens qui nous oblige à l'utiliser?

C et C++ utilisent des schémas différents pour nommer les symboles. Cela indique à l'éditeur de liens d'utiliser le schéma de C lors de la liaison dans la bibliothèque donnée.

Comment, en termes de compilation/mise en lien, cela résout-il les problèmes qui nous obligent à l’utiliser?

L'utilisation du schéma de nommage C vous permet de référencer des symboles de style C. Sinon, l'éditeur de liens essaierait des symboles de style C++ qui ne fonctionneraient pas.

11
Tony M

C et C++ ont des règles différentes sur les noms de symboles. Les symboles permettent au lieur de savoir que l'appel à la fonction "openBankAccount" dans un fichier objet produit par le compilateur est une référence à cette fonction que vous avez appelée "openBankAccount" dans un autre fichier objet produit à partir d'un fichier source différent par le même (ou compatible) compilateur. Cela vous permet de créer un programme à partir de plusieurs fichiers source, ce qui est un soulagement lorsque vous travaillez sur un grand projet.

En C, la règle est très simple: les symboles sont tous dans un seul espace de noms. Ainsi, le nombre entier "chaussettes" est stocké en tant que "chaussettes" et la fonction count_socks est stockée sous la forme "count_socks".

Les lieurs ont été construits pour C et d'autres langages comme C avec cette simple règle de nommage des symboles. Les symboles dans l'éditeur de liens ne sont donc que de simples chaînes.

Mais en C++, le langage vous permet d’avoir des espaces de noms, du polymorphisme et diverses autres choses en conflit avec une règle aussi simple. Les six fonctions polymorphes appelées "add" doivent avoir des symboles différents, sinon le mauvais sera utilisé par d'autres fichiers objets. Cela se fait en "manipulant" (c'est un terme technique) les noms des symboles.

Lorsque vous liez du code C++ à des bibliothèques C ou à du code, vous avez besoin de extern "C", écrit en C, tels que les fichiers d’en-tête des bibliothèques C, pour indiquer à votre compilateur C++ que ces noms de symboles ne doivent pas être mutilés, votre code C++ doit bien sûr être mutilé, sinon cela ne fonctionnera pas.

10
tialaramex

Le compilateur C++ crée des noms de symbole différemment du compilateur C. Ainsi, si vous essayez d'appeler une fonction située dans un fichier C, compilée en tant que code C, vous devez indiquer au compilateur C++ que les noms de symboles qu'il tente de résoudre ont une apparence différente de celle par défaut; sinon, l'étape du lien échouera.

7
mbyrne215

Vous devez utiliser extern "C" chaque fois que vous incluez un en-tête définissant les fonctions résidant dans un fichier compilé par un compilateur C, utilisé dans un fichier C++. (De nombreuses bibliothèques C standard peuvent inclure cette vérification dans leurs en-têtes afin de simplifier les choses pour le développeur)

Par exemple, si vous avez un projet avec 3 fichiers, util.c, util.h et main.cpp et que les fichiers .c et .cpp sont compilés avec le compilateur C++ (g ++, cc, etc.), alors ce n'est pas le cas. t vraiment nécessaire, et peut même causer des erreurs de l'éditeur de liens. Si votre processus de construction utilise un compilateur C standard pour util.c, vous devrez utiliser extern "C" pour inclure util.h.

Ce qui se passe, c'est que C++ code les paramètres de la fonction en son nom. Voici comment fonctionne la surcharge de fonctions. Tout ce qui tend à arriver à une fonction C est l’ajout d’un trait de soulignement ("_") au début du nom. Sans utiliser extern "C", l'éditeur de liens recherchera une fonction nommée DoSomething @@ int @ float () lorsque le nom réel de la fonction est _DoSomething () ou simplement DoSomething ().

L'utilisation de extern "C" résout le problème ci-dessus en indiquant au compilateur C++ qu'il doit rechercher une fonction conforme à la convention de nommage C au lieu de la convention C++.

7
HitScan

Le extern "C" {} _ construct demande au compilateur de ne pas modifier les noms déclarés entre accolades. Normalement, le compilateur C++ "améliore" les noms de fonction afin qu'ils codent des informations de type sur les arguments et la valeur de retour; c'est ce qu'on appelle le nom mutilé. Le extern "C" _ construction empêche le brassage.

Il est généralement utilisé lorsque le code C++ doit appeler une bibliothèque de langage C. Il peut également être utilisé lors de l'exposition d'une fonction C++ (à partir d'une DLL, par exemple) à des clients C.

6
Paul Lalonde

Ceci est utilisé pour résoudre les problèmes de gestion des noms. extern C signifie que les fonctions sont dans une API "plate" de style C.

5
Eric Z Beard

Décompiler un g++ binaire généré pour voir ce qui se passe

Je vais dans cette réponse de: Quel est l'effet de l'externe "C" en C++? puisque cette question a été considérée comme une copie de celle-ci.

main.cpp

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Compiler avec GCC 4.8 Linux ELF sortie:

g++ -c main.cpp

Décompiler la table des symboles:

readelf -s main.o

La sortie contient:

Num:    Value          Size Type    Bind   Vis      Ndx Name
  8: 0000000000000000     6 FUNC    GLOBAL DEFAULT    1 _Z1fv
  9: 0000000000000006     6 FUNC    GLOBAL DEFAULT    1 ef
 10: 000000000000000c    16 FUNC    GLOBAL DEFAULT    1 _Z1hv
 11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
 12: 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 pas 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

Donc vous aurez besoin de extern "C" les deux lors de l'appel:

  • C depuis C++: dire g++ s'attendre à des symboles non mélangés produits par gcc
  • C++ à partir de C: dire g++ pour 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 à l'intérieur de 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 pour autant extern "C" le lien échoue avec:

main.cpp:6: undefined reference to `f()'

car 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

Appeler C++ depuis est un peu plus difficile: nous devons créer manuellement des versions non modifiables de chaque fonction que nous souhaitons 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 pour autant extern "C" 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 que gcc ne peut pas trouver.

Exemple sur GitHub .

Testé dans Ubuntu 18.04.