Descompile un g++
binario generado para ver qué está pasando
Para comprender por qué extern
es necesario, lo mejor que puede hacer es comprender lo que está sucediendo en detalle en los archivos de objetos con un ejemplo:
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Compile con la salida GCC 4.8 Linux ELF :
g++ -c main.cpp
Descompilar la tabla de símbolos:
readelf -s main.o
La salida contiene:
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
Interpretación
Vemos eso:
ef
y eg
se almacenaron en símbolos con el mismo nombre que en el código
los otros símbolos fueron destrozados. Vamos a deshacerlos:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Conclusión: los dos tipos de símbolos siguientes no fueron destrozados:
- definido
- declarado pero indefinido (
Ndx = UND
), que se proporcionará en el enlace o tiempo de ejecución desde otro archivo de objeto
Entonces necesitará extern "C"
ambos cuando llame:
- C de C ++: decir
g++
que espere símbolos sin desencadenar producidos porgcc
- C ++ de C: diga
g++
que genere símbolos sin desenvolver para gcc
usar
Cosas que no funcionan en el exterior C
Resulta obvio que cualquier característica de C ++ que requiera el cambio de nombre no funcionará en el interior 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) { }
}
Ejemplo mínimo de C ejecutable desde C ++
En aras de la exhaustividad y para los novatos, vea también: ¿Cómo usar archivos fuente C en un proyecto C ++?
Llamar a C desde C ++ es bastante fácil: cada función de C solo tiene un posible símbolo no mutilado, por lo que no se requiere trabajo adicional.
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; }
Correr:
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
Sin extern "C"
el enlace falla con:
main.cpp:6: undefined reference to `f()'
porque g++
espera encontrar un destrozado f
, que gcc
no produjo.
Ejemplo en GitHub .
Ejemplo de C ++ ejecutable mínimo desde C
Llamar a C ++ desde es un poco más difícil: tenemos que crear manualmente versiones no mutiladas de cada función que queremos exponer.
Aquí ilustramos cómo exponer sobrecargas de la función C ++ a C.
C Principal
#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);
}
Correr:
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
Sin extern "C"
falla con:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
porque g++
generó símbolos destrozados que gcc
no pueden encontrar.
Ejemplo en GitHub .
Probado en Ubuntu 18.04.