it-swarm-es.tech

¿Por qué necesitamos extern "C" {#include <foo.h>} en C++?

¿Por qué necesitamos usar:

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

Específicamente:

  • ¿Cuándo debemos usarlo?

  • ¿Qué está sucediendo en el nivel del compilador/vinculador que nos obliga a usarlo?

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

129
Landon

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 está esperando el código de C++. Sin embargo, si es un encabezado C, el compilador espera que los datos contenidos en el archivo del encabezado se compilen en un formato determinado: el C++ 'ABI' o la 'Interfaz binaria de la 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 vida, el ABI de C++ generalmente 'maneja' los nombres de sus funciones/métodos, por lo que llamar a printf() sin marcar el prototipo como una función de C, el C++ en realidad generará un código de llamada _Zprintf, más una mierda extra en la fin.)

Entonces: use extern "C" {...} cuando incluya un encabezado c, es así de simple. De lo contrario, tendrá una discrepancia en el código compilado, y el enlazador se ahogará. Sin embargo, para la mayoría de los encabezados, ni siquiera necesitará extern porque la mayoría de los encabezados del sistema C ya explicarán el hecho de que podrían estar incluidos en el código C++ y ya extern su código.

116
duane

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

Dado test.C como tal:

void foo() { }

Compilar y listar símbolos en el archivo objeto da:

$ 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 cambio escribes test.C como este:

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

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

Por lo general, incluye un encabezado en extern "C" {} si el código que lo acompaña se compiló con un compilador de C, pero está 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 vincules tu código, tus archivos .o contendrán referencias a "foo", no a "_Z3fooblah", que, con suerte, coincidirá con lo que haya en la biblioteca a la que te vinculas.

La mayoría de las bibliotecas modernas colocarán guardias 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 asegura que cuando el código C++ incluya el encabezado, los símbolos en su archivo de objeto coincidan con lo que hay en la biblioteca de C. Solo debería tener que colocar una "C" externa {} alrededor del encabezado de C si es antiguo y no tiene estos guardias ya.

107
Todd Gamblin

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

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

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

extern "C" le dice al compilador de C++ que no realice ninguna manipulación de nombres en el código dentro de las llaves. Esto le permite llamar a funciones C desde C++.

21
Trent

Tiene que ver con la forma en que los diferentes compiladores realizan gestos de nombres. Un compilador de C++ modificará 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, de modo que cuando intente vincular, obtendrá un error de vinculador que indica que faltan símbolos.

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

14
1800 INFORMATION

¿Cuándo debemos usarlo?

Cuando está enlazando bibliotecas C en archivos de objetos C++

¿Qué está sucediendo en el nivel del compilador/vinculador que nos obliga a usarlo?

C y C++ usan diferentes esquemas para nombrar los símbolos. Esto le dice al enlazador que use el esquema de C cuando se vincula en la biblioteca dada.

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

El uso del esquema de denominación de C le permite hacer referencia a los símbolos de estilo C. De lo contrario, el enlazador intentaría símbolos de estilo C++ que no funcionarían.

11
Tony M

C y C++ tienen diferentes reglas sobre los nombres de los símbolos. Los símbolos son cómo el enlazador sabe que la llamada a la función "openBankAccount" en un archivo de objeto producido por el compilador es una referencia a esa función que llamó "openBankAccount" en otro archivo de objeto producido desde un archivo fuente diferente por el mismo (o compatible) compilador. Esto le permite hacer un programa 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, todos los símbolos están en un solo espacio de nombre de todos modos. Así que el entero "calcetines" se almacena como "calcetines" y la función count_socks se almacena como "count_socks".

Los enlazadores se crearon para C y otros idiomas como C con esta sencilla regla de denominación de símbolos. Así que los símbolos en el enlazador son simples cadenas.

Pero en C++, el lenguaje le permite tener espacios de nombres, polimorfismo y varias otras cosas que entran en conflicto con una regla tan simple. Todas las seis de sus funciones polimórficas llamadas "agregar" necesitan tener símbolos diferentes, o el archivo incorrecto será usado por otros archivos de objetos. Esto se hace "destrozando" (es un término técnico) los nombres de los símbolos.

Al vincular el código de C++ con las bibliotecas o el código de C, necesita algo externo en "C" escrito en C, como los archivos de encabezado de las bibliotecas de C, para decirle a su compilador de C++ que estos nombres de símbolos no deben modificarse, mientras que el resto de su código C++, por supuesto, debe ser mutilado o no funcionará.

10
tialaramex

El compilador de C++ crea nombres de símbolos de manera diferente al compilador de C. Por lo tanto, si está tratando de hacer una llamada a una función que reside en un archivo C, compilado como código C, necesita decirle al compilador de C++ que los nombres de los símbolos que está tratando de resolver son diferentes a los predeterminados; De lo contrario, el paso de enlace fallará.

7
mbyrne215

Debe usar extern "C" en cualquier momento que incluya un encabezado que define funciones que residen en un archivo compilado por un compilador de C, usado en un archivo de C++. (Muchas bibliotecas estándar de C pueden incluir esta comprobación en sus encabezados para que sea más simple 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 se compilan con el compilador de C++ (g ++, cc, etc), entonces no es Realmente se necesita, e incluso puede causar errores en el enlazador. Si su proceso de compilación usa un compilador de C normal para util.c, entonces necesitará usar la "C" externa cuando incluya 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. Todo lo que suele suceder con una función de C es la adición de un guión bajo ("_") al principio del nombre. Sin usar la "C" externa, el enlazador buscará una función llamada DoSomething @@ int @ float () cuando el nombre real de la función sea _DoSomething () o simplemente DoSomething ().

El uso de la "C" externa resuelve el problema anterior al decirle 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++.

7
HitScan

La construcción extern "C" {} le indica al compilador que no realice la manipulación de nombres 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 denomina nombre destrozado . La construcción extern "C" previene la mutilación.

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

6
Paul Lalonde

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

5
Eric Z Beard

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

Me estoy moviendo en esta respuesta de: ¿Cuál es el efecto de extern "C" en C++? Desde que esa pregunta fue considerada un duplicado de esta.

main.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 ELF salida:

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 desenmarañarlos:

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

Conclusión: ambos de los siguientes tipos de símbolos fueron no mutilados:

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

Así que necesitarás extern "C" tanto cuando llames:

  • C de C++: diga a g++ que espere símbolos no enredados producidos por gcc
  • C++ de C: diga a g++ que genere símbolos no enredados para que gcc use

Cosas que no funcionan en extern C

Resulta obvio que cualquier característica de C++ que requiera la manipulación de nombres no funcionará dentro 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 mínimo ejecutable desde C++ ejemplo

En aras de la integridad y de las noticias que hay por ahí, vea también: ¿Cómo usar los archivos fuente de C en un proyecto de C++?

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

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

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 una f mutilada, que gcc no produjo.

Ejemplo en GitHub .

Ejecución mínima de C++ desde C ejemplo

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

Aquí ilustramos cómo exponer 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 mutilados que gcc no puede encontrar.

Ejemplo en GitHub .

Probado en Ubuntu 18.04.