Décompilez un g++
binaire généré pour voir ce qui se passe
Pour comprendre pourquoi extern
est nécessaire, la meilleure chose à faire est de comprendre ce qui se passe en détail dans les fichiers objets avec un exemple:
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 avec la sortie ELF Linux GCC 4.8 :
g++ -c main.cpp
Décompilez 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
ont été stockés dans des symboles avec le même nom que dans le code
les autres symboles étaient 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:
- défini
- déclaré mais non défini (
Ndx = UND
), à fournir au moment du lien ou de l'exécution à partir d'un autre fichier objet
Vous aurez donc besoin des extern "C"
deux lors de l'appel:
- C à partir de C ++: dites
g++
d'attendre des symboles démêlés produits pargcc
- C ++ à partir de C: dire
g++
de générer des symboles démêlés gcc
à utiliser
Choses qui ne fonctionnent pas dans extern C
Il devient évident que toute fonctionnalité C ++ nécessitant une modification des noms 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 de C exécutable minimal à partir de C ++
Par souci d'exhaustivité et pour les nouveautés, voir aussi: Comment utiliser des 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++. */
#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 sur GitHub .
Exemple C ++ exécutable minimal à partir de C
Appeler C ++ depuis est un peu plus difficile: nous devons créer manuellement des versions non mutilées de chaque fonction que nous voulons exposer.
Ici, nous illustrons 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 échoue avec:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
parce que g++
généré des symboles mutilés qui gcc
ne peuvent pas trouver.
Exemple sur GitHub .
Testé dans Ubuntu 18.04.