Question

Pourquoi devons-nous utiliser:

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

Spécifiquement:

  • Quand devrions-nous l'utiliser?

  • Que se passe-t-il au niveau du compilateur / de l'éditeur de liens pour que nous l'utilisions?

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

Était-ce utile?

La solution

C et C ++ sont superficiellement similaires, mais chacun est compilé 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 en tant que fonction C, C ++ va réellement générer du code appelant _Zprintf , ainsi que de la merde supplémentaire à la fin.)

Donc: utilisez extern " C " {...} lors de l'inclusion d'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.

Autres conseils

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.

Étant donné que test.C ressemble à ceci:

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 est en fait appelée "_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() { }
}

Puis 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 du " foo " La fonction dans le fichier objet est simplement "foo", et elle ne contient pas toutes les informations de type fantaisie qui proviennent de nom mangling.

En règle générale, vous incluez 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 contiennent des références à "foo", pas "_Z3fooblah", qui correspond, espérons-le, à tout ce qui se trouve 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 devriez seulement avoir à mettre extern " C " {} autour de votre en-tête C s'il est ancien et qu'il ne possède pas déjà ces gardes.

En C ++, vous pouvez avoir différentes entités partageant 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, le compilateur C ++ créera des noms uniques pour chacun d’entre eux au cours d’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 entre accolades. Cela vous permet d’appeler des fonctions C à partir de C ++.

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 qui indique qu'il manque des symboles.

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

  

Quand devrions-nous l'utiliser?

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

  

Que se passe-t-il au   niveau compilateur / lieur 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 / liaison   cela résout-il les problèmes   nous obliger à 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.

C et C ++ ont des règles différentes sur les noms de symboles. Les symboles indiquent comment l’éditeur de liens sait que l’appel à la fonction "openBankAccount" dans un fichier objet produit par le compilateur est une référence à la fonction que vous avez appelée "openBankAccount". dans un autre fichier objet produit à partir d'un fichier source différent par le même compilateur (ou compatible). Cela vous permet de créer un programme à partir de plusieurs fichiers source, ce qui est un avantage 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. Donc, 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. Vos six fonctions polymorphes sont appelées & add; add " besoin d'avoir différents symboles, sinon le mauvais sera utilisé par d'autres fichiers objets. Ceci est fait par " mangling " (c’est un terme technique) les noms des symboles.

Lorsque vous liez du code C ++ à des bibliothèques ou à du code C, vous devez utiliser extern "C". tout ce qui est écrit en C, comme les fichiers d’en-tête des bibliothèques C, indique à votre compilateur C ++ que ces noms de symboles ne doivent pas être mutilés, alors que le reste de votre code C ++ doit bien sûr être mutilé ou il ne fonctionnera pas.

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 la tâche du 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 tous deux compilés avec le compilateur C ++ (g ++, cc, etc.), puis ce n'est pas vraiment nécessaire, et peut même causer des erreurs d'éditeur de liens. Si votre processus de construction utilise un compilateur C standard pour util.c, vous devrez utiliser extern "C". en incluant 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 ().

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 ++.

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 symbole qu'il tente de résoudre ont une apparence différente de celle par défaut; sinon, l'étape du lien échouera.

Le extern " C " La construction {} indique 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 encodent les informations de type concernant les arguments et la valeur de retour; c'est ce qu'on appelle le nom mutilé . La construction extern " "C" empêche la modification.

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.

Ceci est utilisé pour résoudre les problèmes de gestion de noms. extern C signifie que les fonctions se trouvent dans un "appartement". API de style C.

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

Je me déplace dans cette réponse de: Quel est l’effet de extern " C " en C ++? puisque cette question était 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

Nous constatons que:

  • ef et , par exemple , étaient stockés dans des symboles portant le même nom que dans le code

  • les autres symboles ont été mutilés. Démêchons-les:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Conclusion: les deux types de symboles suivants n'é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

Vous aurez donc besoin de extern " "C" pour appeler:

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

Ce qui ne fonctionne pas dans l'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) { }
}

Exemple minimal de C exécutable à partir de l'exemple 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 possible, donc aucun travail supplémentaire n'est requis.

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; }

Exécuter:

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 f mutilé, que gcc n'a pas généré.

Exemple sur GitHub . >

Exemple minimal exécutable C ++ à partir de C

L'appel de C ++ depuis 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 ++ à C.

main.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);
}

Exécuter:

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'

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

Exemple sur GitHub . >

Testé sous Ubuntu 18.04.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top