ejemplo - extern c++
¿Por qué necesitamos extern "C"{#include<foo.h>} en C++? (10)
Esta pregunta ya tiene una respuesta aquí:
- ¿Cuál es el efecto de extern "C" en C ++? 12 respuestas
¿Por qué tenemos que usar:
extern "C" {
#include <foo.h>
}
Específicamente:
¿Cuándo deberíamos usarlo?
¿Qué está sucediendo al nivel de compilador / enlazador que nos obliga a usarlo?
¿Cómo en términos de compilación / vinculación resuelve esto los problemas que requieren que lo usemos?
¿Cuándo deberíamos usarlo?
Cuando está enlazando bibliotecas C en archivos de objeto C ++
¿Qué está sucediendo al nivel de compilador / enlazador que nos obliga a usarlo?
C y C ++ usan diferentes esquemas para nombrar símbolos. Esto le dice al vinculador que use el esquema de C al enlazar en la biblioteca dada.
¿Cómo en términos de compilación / vinculación resuelve esto los problemas que requieren que lo usemos?
El uso del esquema de denominación C le permite hacer referencia a los símbolos estilo C. De lo contrario, el enlazador probaría símbolos de estilo C ++ que no funcionarían.
C y C ++ son superficialmente similares, pero cada uno compila en un conjunto de códigos muy diferente. Cuando incluye un archivo de encabezado con un compilador C ++, el compilador espera el código C ++. Sin embargo, si se trata de un encabezado C, el compilador espera que los datos contenidos en el archivo de encabezado se compilen en un determinado formato, el C ++ ''ABI'' o ''Application Binary Interface'', por lo que el enlazador se ahoga. Esto es preferible a pasar datos de C ++ a una función que espera datos de C.
(Para entrar en lo realmente esencial, el ABI de C ++ generalmente "descifra" los nombres de sus funciones / métodos, así que al llamar a printf()
sin marcar el prototipo como una función C, C ++ generará llamadas de código _Zprintf
, más mierda extra al final.)
Entonces: use extern "C" {...};
cuando se incluye el encabezado de CA, es así de simple. De lo contrario, tendrá una falta de coincidencia en el código compilado y el enlazador se ahogará. Para la mayoría de los encabezados, sin embargo, ni siquiera necesitará 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.
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 objeto producido por el compilador es una referencia a esa función llamada "openBankAccount" en otro archivo objeto producido desde 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 que es un alivio cuando se trabaja en un proyecto grande.
En C, la regla es muy simple, los símbolos están todos en un solo espacio de nombres de todos modos. Entonces, el entero "calcetines" se almacena como "calcetines" y la función cuenta_calcetines se almacena como "cuellos_contables".
Los enlazadores se crearon para C y otros lenguajes como C con esta regla de denominación de símbolos simple. Entonces los símbolos en el enlazador son solo cadenas simples.
Pero en C ++, el lenguaje te permite tener espacios de nombres y polimorfismo y varias otras cosas que entran en conflicto con una regla tan simple. Las seis funciones polimórficas llamadas "agregar" deben tener diferentes símbolos, o la incorrecta será utilizada por otros archivos de objetos. Esto se hace mediante el "mangle" (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 todo lo externo "C" escrito en C, como archivos de encabezado para las bibliotecas de C, decirle a su compilador de C ++ que estos nombres de símbolos no se deben destruir, mientras que el resto su código C ++, por supuesto, debe ser mutilado o no funcionará.
Debe usar extern "C" cada vez que incluya un encabezado que defina funciones que residen en un archivo compilado por un compilador de C, utilizado en un archivo C ++. (Muchas bibliotecas C estándar pueden incluir esta verificación en sus encabezados para que el desarrollador sea más simple)
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 C ++ (g ++, cc, etc.), entonces no es realmente es necesario, e incluso puede causar errores de enlazador. Si su proceso de compilación usa un compilador de C normal para util.c, necesitará usar extern "C" 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 tiende a sucederle a una función C es la adición de un guión bajo ("_") al comienzo del nombre. Sin usar la opción "C", 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 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 ++.
El compilador de C ++ crea nombres de símbolos de forma diferente que el compilador de C. Por lo tanto, si intenta hacer 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 tienen un aspecto diferente al predeterminado; de lo contrario, el paso del enlace fallará.
En C ++, puede 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 a todos, el compilador de C ++ creará nombres únicos para cada uno en un proceso llamado mangle o decoración. Los compiladores C no hacen esto. Además, cada compilador de C ++ puede hacer esto de otra manera.
Extern "C" le dice al compilador de C ++ que no realice ningún cambio de nombre en el código dentro de los corchetes. Esto le permite llamar a las funciones C desde dentro de C ++.
Esto se usa para resolver problemas de creación de nombres. Extern C significa que las funciones están en una API de estilo C "plana".
Extern "C" determina cómo deben nombrarse los símbolos en el archivo de objeto generado. Si se declara una función sin extern "C", el nombre del símbolo en el archivo del objeto usará el cambio de nombre de C ++. Aquí hay un ejemplo.
Prueba dada. C como tal:
void foo() { }
La compilación y el listado de símbolos en el archivo de objeto da:
$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
U __gxx_personality_v0
La función foo se llama realmente "_Z3foov". Esta cadena contiene información de tipo para el tipo de retorno y los parámetros, entre otras cosas. Si en su lugar escribes test.C les gusta esto:
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 de objeto es solo "foo", y no tiene toda la información de tipo elegante que proviene de la creación de nombres.
Generalmente, se incluye un encabezado dentro de la opción "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 vincula su código, sus archivos .o contendrán referencias a "foo", no a "_Z3fooblah", lo que con suerte coincidirá con lo que esté en la biblioteca con la que está enlazando.
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. por ejemplo, en muchos de los encabezados estándar que encontrarás:
#ifdef __cplusplus
extern "C" {
#endif
... declarations ...
#ifdef __cplusplus
}
#endif
Esto asegura que cuando el código de C ++ incluye el encabezado, los símbolos en su archivo de objeto coinciden con lo que está en la biblioteca de C. Solo debe colocar extern "C" {} alrededor de su encabezado C si es antiguo y ya no tiene estas protecciones.
La construcción extern "C" {}
ordena al compilador que no realice cambios en los nombres declarados dentro de las llaves. Normalmente, el compilador de C ++ "mejora" los nombres de las funciones para que codifiquen la información de tipo sobre los argumentos y el valor de retorno; esto se llama el nombre destrozado . El constructo extern "C"
previene la destrucción.
Normalmente se usa cuando el código C ++ necesita llamar a una biblioteca de lenguaje C. También se puede usar al exponer una función de C ++ (desde una DLL, por ejemplo) a clientes C.
Tiene que ver con la forma en que los diferentes compiladores realizan el cambio de nombre. Un compilador de C ++ destruirá el nombre de un símbolo exportado desde el archivo de encabezado de una manera completamente diferente a la de un compilador de C, de modo que cuando intente vincularse, obtendrá un error de enlazador 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 el cambio de nombre de la misma forma que el compilador de C. Una vez hecho esto, los errores del enlazador son corregidos.