Pregunta

¿Por qué necesitamos usar?

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

Específicamente:

  • ¿Cuándo debemos usarlo?

  • ¿Qué está sucediendo en el nivel del compilador/enlazador que requiere que lo usemos?

  • ¿Cómo, en términos de compilación/vinculación, esto resuelve los problemas que requieren que lo usemos?

¿Fue útil?

Solución

C y C++ son superficialmente similares, pero cada uno se compila en un conjunto de código muy diferente.Cuando incluye un archivo de encabezado con un compilador de C++, el compilador espera código C++.Sin embargo, si es un encabezado C, entonces el compilador espera que los datos contenidos en el archivo de encabezado se compilen en un formato determinado: el 'ABI' de C++ o 'Interfaz binaria de aplicación', por lo que el vinculador se atasca.Esto es preferible a pasar datos de C++ a una función que espera datos de C.

(Para entrar en el meollo de la cuestión, la ABI de C++ generalmente 'destroza' los nombres de sus funciones/métodos, por lo que llamar printf() sin marcar el prototipo como una función C, C++ realmente generará llamadas de código _Zprintf, más basura extra al final.)

Entonces:usar extern "C" {...} al incluir un encabezado c, es así de simple.De lo contrario, el código compilado no coincidirá y el vinculador se bloqueará.Sin embargo, para la mayoría de los encabezados, ni siquiera necesitarás el extern porque la mayoría de los encabezados del sistema C ya tendrán en cuenta el hecho de que podrían estar incluidos en el código C++ y ya extern su código.

Otros consejos

La "C" externa determina cómo se deben nombrar los símbolos en el archivo objeto generado.Si una función se declara sin "C" externa, el nombre del símbolo en el archivo objeto utilizará la manipulación de nombres de C++.He aquí un ejemplo.

Dado test.C así:

void foo() { }

Al compilar y enumerar símbolos en el archivo objeto se obtiene:

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

La función foo en realidad se llama "_Z3foov".Esta cadena contiene información de tipo para el tipo de retorno y los parámetros, entre otras cosas.Si en lugar de eso escribes test.C así:

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

Luego compila y mira los símbolos:

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

Obtienes un enlace C.El nombre de la función "foo" en el archivo objeto es simplemente "foo", y no tiene toda la información sofisticada que proviene de la manipulación de nombres.

Generalmente incluyes un encabezado dentro de "C" externo {} si el código que lo acompaña fue compilado con un compilador de C pero estás intentando llamarlo desde C++.Cuando haces esto, le estás diciendo al compilador que todas las declaraciones en el encabezado usarán el enlace C.Cuando vincula su código, sus archivos .o contendrán referencias a "foo", no a "_Z3fooblah", que con suerte coincide con lo que esté en la biblioteca a la que está vinculando.

La mayoría de las bibliotecas modernas pondrán protectores alrededor de dichos encabezados para que los símbolos se declaren con el enlace correcto.p.ej.En muchos de los encabezados estándar encontrarás:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Esto garantiza que cuando el código C++ incluya el encabezado, los símbolos en su archivo objeto coincidan con los que hay en la biblioteca C.Solo deberías tener que poner "C" {} externa alrededor de tu encabezado C si es antiguo y aún no tiene estas protecciones.

En C++, puedes tener diferentes entidades que comparten un nombre.Por ejemplo, aquí hay una lista de funciones, todas nombradas foo:

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

Para diferenciarlos a todos, el compilador de C++ creará nombres únicos para cada uno en un proceso llamado alteración o decoración de nombres.Los compiladores de C no hacen esto.Además, cada compilador de C++ puede hacer esto de una manera diferente.

"C" externo le dice al compilador de C++ que no realice ninguna modificación de nombres en el código entre llaves.Esto le permite llamar a funciones C desde C++.

Tiene que ver con la forma en que los diferentes compiladores modifican los nombres.Un compilador de C++ alterará el nombre de un símbolo exportado desde el archivo de encabezado de una manera completamente diferente a como lo haría un compilador de C, por lo que cuando intente vincular, obtendrá un error del vinculador que indica que faltan símbolos.

Para resolver esto, le decimos al compilador de C++ que se ejecute en modo "C", para que realice la manipulación de nombres de la misma manera que lo haría el compilador de C.Una vez hecho esto, se solucionan los errores del vinculador.

¿Cuándo debemos usarlo?

Cuando vincula bibliotecas de C a archivos de objetos de C++

¿Qué está sucediendo a nivel de compilador/enlazador que requiere que lo usemos?

C y C++ utilizan diferentes esquemas para la denominación de símbolos.Esto le dice al vinculador que use el esquema de C al vincular en la biblioteca dada.

¿Cómo en términos de compilación/enlace resuelve los problemas que requieren que los usemos?

El uso del esquema de nombres C le permite hacer referencia a símbolos de estilo C.De lo contrario, el vinculador probaría símbolos de estilo C++ que no funcionarían.

C y C++ tienen reglas diferentes sobre los nombres de los símbolos.Los símbolos son la forma en que el vinculador sabe que la llamada a la función "openBankAccount" en un archivo objeto producido por el compilador es una referencia a esa función que usted llamó "openBankAccount" en otro archivo objeto producido a partir de un archivo fuente diferente por el mismo (o compatible) compilador.Esto le permite crear un programa a partir de más de un archivo fuente, lo cual es un alivio cuando se trabaja en un proyecto grande.

En C, la regla es muy simple: de todos modos, los símbolos están todos en un solo espacio de nombre.Entonces, el número entero "calcetines" se almacena como "calcetines" y la función contar_calcetines se almacena como "contar_calcetines".

Los enlazadores se crearon para C y otros lenguajes como C con esta sencilla regla de nomenclatura de símbolos.Entonces los símbolos en el enlazador son simplemente cadenas.

Pero en C++ el lenguaje te permite tener espacios de nombres, polimorfismo y varias otras cosas que entran en conflicto con una regla tan simple.Las seis funciones polimórficas llamadas "agregar" deben tener símbolos diferentes, o otros archivos de objetos utilizarán el incorrecto.Esto se hace "destrozando" (ese es un término técnico) los nombres de los símbolos.

Al vincular código C++ a bibliotecas o código C, necesita "C" externo, cualquier cosa escrita en C, como archivos de encabezado para las bibliotecas C, para indicarle a su compilador de C++ que estos nombres de símbolos no deben alterarse, mientras que el resto de su código C++, por supuesto, debe estar alterado o no funcionará.

Debe usar "C" externo cada vez que incluya un encabezado que defina funciones que residen en un archivo compilado por un compilador de C, usado en un archivo C++.(Muchas bibliotecas C estándar pueden incluir esta verificación en sus encabezados para que sea más sencillo para el desarrollador)

Por ejemplo, si tiene un proyecto con 3 archivos, util.c, util.h y main.cpp y los archivos .c y .cpp están compilados con el compilador de C++ (g++, cc, etc.), entonces no es así. No es realmente necesario e incluso puede causar errores en el vinculador.Si su proceso de compilación utiliza un compilador de C normal para util.c, entonces necesitará usar "C" externo al incluir util.h.

Lo que sucede es que C++ codifica los parámetros de la función en su nombre.Así es como funciona la sobrecarga de funciones.Lo único que suele pasarle a una función C es añadir un guión bajo ("_") al principio del nombre.Sin utilizar "C" externa, el vinculador buscará una función llamada DoSomething@@int@float() cuando el nombre real de la función sea _DoSomething() o simplemente DoSomething().

El uso de "C" externo resuelve el problema anterior diciéndole al compilador de C++ que debe buscar una función que siga la convención de nomenclatura de C en lugar de la de C++.

El compilador de C++ crea nombres de símbolos de forma diferente que el compilador de C.Entonces, si está intentando realizar una llamada a una función que reside en un archivo C, compilado como código C, debe decirle al compilador de C++ que los nombres de los símbolos que está tratando de resolver se ven diferentes a los predeterminados;de lo contrario, el paso de vinculación fallará.

El extern "C" {} La construcción indica al compilador que no realice modificaciones en los nombres declarados entre llaves.Normalmente, el compilador de C++ "mejora" los nombres de las funciones para que codifiquen información de tipo sobre los argumentos y el valor de retorno;esto se llama el nombre destrozado.El extern "C" La construcción evita que se deforme.

Normalmente se utiliza cuando el código C++ necesita llamar a una biblioteca en lenguaje C.También se puede utilizar al exponer una función de C++ (de una DLL, por ejemplo) a clientes de C.

Esto se utiliza para resolver problemas de manipulación de nombres.C externo significa que las funciones están en una API de estilo C "plana".

Descompilar un g++ binario generado para ver qué está pasando

Me muevo en esta respuesta desde: ¿Cuál es el efecto de la "C" externa en C++? ya que esa pregunta se consideró un duplicado de esta.

principal.cpp

void f() {}
void g();

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

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

Compilar con GCC 4.8 Linux DUENDE producción:

g++ -c main.cpp

Descompila 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 estaban destrozados.Vamos a destrozarlos:

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

Conclusión:Los dos tipos de símbolos siguientes fueron no destrozado:

  • definido
  • declarado pero indefinido (Ndx = UND), que se proporcionará en el enlace o en tiempo de ejecución desde otro archivo objeto

Entonces necesitarás extern "C" ambos al llamar:

  • C de C++:decir g++ esperar símbolos no destrozados producidos por gcc
  • C++ de C:decir g++ para generar símbolos no destrozados para gcc usar

Cosas que no funcionan en C externo

Resulta obvio que cualquier característica de C++ que requiera manipulación de nombres no funcionará dentro 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 a partir de C++

En aras de la exhaustividad y para los novatos, consulte también: ¿Cómo utilizar archivos fuente C en un proyecto C++?

Llamar a C desde C++ es bastante fácil:cada función C solo tiene un símbolo posible no destrozado, por lo que no se requiere trabajo adicional.

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

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, cual gcc no produjo.

Ejemplo en GitHub.

C++ ejecutable mínimo a partir del ejemplo de C

Llamar a C++ desde es un poco más difícil:Tenemos que crear manualmente versiones no alteradas de cada función que queremos exponer.

Aquí ilustramos cómo exponer las sobrecargas de funciones de 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 puede encontrar.

Ejemplo en GitHub.

Probado en Ubuntu 18.04.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top