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 el código de C ++. Sin embargo, si se trata de un encabezado C, el compilador espera que los datos contenidos en el archivo del encabezado se compilen en un formato determinado: el 'ABI' o 'Interfaz binaria de la aplicación' de C ++, por lo que el enlazador se bloquea. 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, el ABI de C ++ generalmente 'manipula' los nombres de sus funciones / métodos, por lo que al llamar printf()sin marcar el prototipo como una función de C, el C ++ generará llamadas de código _Zprintf, más basura al final. )
Entonces: utilícelo extern "C" {...}cuando incluya un encabezado de CA: así de simple. De lo contrario, tendrá una falta de coincidencia en el código compilado, y el enlazador se ahogará. Sin embargo, para la mayoría de los encabezados, ni siquiera necesitará externporque la mayoría de los encabezados C del sistema ya tendrán en cuenta el hecho de que podrían estar incluidos en el código C ++ y ya en externsu código.
¿Podría dar más detalles sobre "la mayoría de los encabezados C del sistema ya tendrán en cuenta el hecho de que podrían estar incluidos en el código C ++ y ya externarán su código". ?
Bulat M.
77
@BulatM. Contienen algo como esto: #ifdef __cplusplus extern "C" { #endif Entonces, cuando se incluyen desde un archivo C ++, todavía se tratan como un encabezado C.
Calmarius
111
extern "C" determina cómo se deben nombrar los símbolos en el archivo objeto generado. Si se declara una función sin "C" externa, el nombre del símbolo en el archivo de objeto utilizará el cambio de nombre de C ++. Aquí hay un ejemplo.
Prueba dada. C así:
void foo(){}
Compilar y enumerar símbolos en el archivo objeto proporciona:
$ 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 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 el enlace 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 elegante que proviene del cambio de nombre.
Generalmente, incluye un encabezado dentro de "C" {} externo si el código que lo acompaña fue compilado 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 vincule 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 con la que se está vinculando.
La mayoría de las bibliotecas modernas colocarán protectores alrededor de dichos encabezados para que los símbolos se declaren con el enlace correcto. por ejemplo, en muchos de los encabezados estándar encontrarás:
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 C. Solo deberías poner "C" {} externo alrededor de tu encabezado C si es viejo y no tiene estos guardias ya.
En C ++, puede tener diferentes entidades que comparten un nombre. Por ejemplo, aquí hay una lista de funciones todas denominadas 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 decoración o cambio de nombres. 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 ningún cambio de nombre en el código entre llaves. Esto le permite llamar a funciones de C desde C ++.
Tiene que ver con la forma en que los diferentes compiladores realizan el cambio de nombre. Un compilador de C ++ destrozará el nombre de un símbolo exportado desde el archivo de encabezado de una manera completamente diferente a la de un compilador de C, por lo 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 el cambio de nombre de la misma manera que lo haría el compilador de C. Una vez hecho esto, los errores del enlazador son corregidos.
C y C ++ tienen reglas diferentes sobre los nombres de los símbolos. Los símbolos son cómo 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 desde un archivo fuente diferente por el mismo (o compatible) compilador. Esto le permite crear un programa con más de un archivo fuente, lo cual es un alivio cuando trabaja en un proyecto grande.
En C la regla es muy simple, los símbolos están todos en un solo espacio de nombre de todos modos. Por lo tanto, 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 lenguajes como C con esta simple regla de denominación de símbolos. Entonces, los símbolos en el enlazador son simples cadenas.
Pero en C ++, el lenguaje le permite tener espacios de nombres y polimorfismo y otras cosas que entran en conflicto con una regla tan simple. Las seis funciones polimórficas llamadas "agregar" deben tener símbolos diferentes, de lo contrario, otros archivos de objeto utilizarán la incorrecta. Esto se hace "destrozando" (es un término técnico) los nombres de los símbolos.
Al vincular el código de C ++ a las bibliotecas o el código de C, necesita una "C" externa escrita en C, como los archivos de encabezado para las bibliotecas de C ++, para decirle al compilador de C ++ que estos nombres de símbolos no deben ser maltratados, mientras que el resto de su código C ++, por supuesto, debe ser destruido o no funcionará.
Cuando vincula bibliotecas C en archivos de objetos C ++
¿Qué está sucediendo en el nivel del compilador / enlazador que requiere que lo usemos?
C y C ++ usan diferentes esquemas para nombrar símbolos. Esto le dice al vinculador que use el esquema de C cuando se vincula 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 nombres C le permite hacer referencia a símbolos de estilo C. De lo contrario, el enlazador probaría símbolos de estilo C ++ que no funcionarían.
Debe usar "C" externa siempre que incluya un encabezado que defina funciones que residen en un archivo compilado por un compilador de C, usado en un archivo de C ++. (Muchas bibliotecas C estándar pueden incluir esta verificació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 ambos archivos .c y .cpp se compilan con el compilador de C ++ (g ++, cc, etc.) entonces no es No es realmente necesario, e incluso puede causar errores de enlazador. Si su proceso de compilación usa un compilador de C normal para util.c, entonces necesitará usar "C" externa 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. Todo lo que suele sucederle a una función C es agregar un guión bajo ("_") al comienzo del nombre. Sin usar "C" externa, el enlazador buscará una función llamada DoSomething @@ int @ float () cuando el nombre real de la función es _DoSomething () o simplemente DoSomething ().
El uso de "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 manera diferente que el compilador de C. Por lo tanto, 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 tienen un aspecto diferente al predeterminado; de lo contrario, el paso del enlace fallará.
La extern "C" {}construcción le indica al compilador que no realice cambios en los nombres declarados entre llaves. Normalmente, el compilador de C ++ "mejora" los nombres de funciones para que codifiquen la información de tipo sobre argumentos y el valor de retorno; Esto se llama el nombre destrozado . losextern "C" construcción 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 C ++ (desde una DLL, por ejemplo) a clientes C.
Descompile un g++binario generado para ver qué está pasando
Para comprender por qué externes 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();}
Num:ValueSizeTypeBindVisNdxName8:00000000000000006 FUNC GLOBAL DEFAULT 1 _Z1fv
9:00000000000000066 FUNC GLOBAL DEFAULT 1 ef
10:000000000000000c16 FUNC GLOBAL DEFAULT 1 _Z1hv
11:00000000000000000 NOTYPE GLOBAL DEFAULT UND _Z1gv
12:00000000000000000 NOTYPE GLOBAL DEFAULT UND eg
Interpretación
Vemos eso:
efy egse almacenaron en símbolos con el mismo nombre que en el código
los otros símbolos fueron destrozados. Vamos a deshacerlos:
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 gccusar
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 withvoid f();void f(int i);// Templates.// error: template with C linkagetemplate<class C>void f(C i){}}
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */#ifdef __cplusplus
extern"C"{#endifint f();#ifdef __cplusplus
}#endif#endif
#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"{#endifint 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);}
#ifdef __cplusplus extern "C" { #endif
Entonces, cuando se incluyen desde un archivo C ++, todavía se tratan como un encabezado C.extern "C" determina cómo se deben nombrar los símbolos en el archivo objeto generado. Si se declara una función sin "C" externa, el nombre del símbolo en el archivo de objeto utilizará el cambio de nombre de C ++. Aquí hay un ejemplo.
Prueba dada. C así:
Compilar y enumerar símbolos en el archivo objeto proporciona:
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 así:
Luego compila y mira los símbolos:
Obtienes el enlace 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 elegante que proviene del cambio de nombre.
Generalmente, incluye un encabezado dentro de "C" {} externo si el código que lo acompaña fue compilado 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 vincule 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 con la que se está vinculando.
La mayoría de las bibliotecas modernas colocarán protectores alrededor de dichos encabezados para que los símbolos se declaren con el enlace correcto. por ejemplo, en muchos de los encabezados estándar encontrarás:
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 C. Solo deberías poner "C" {} externo alrededor de tu encabezado C si es viejo y no tiene estos guardias ya.
fuente
En C ++, puede tener diferentes entidades que comparten un nombre. Por ejemplo, aquí hay una lista de funciones todas denominadas 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 decoración o cambio de nombres. 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 ningún cambio de nombre en el código entre llaves. Esto le permite llamar a funciones de C desde C ++.
fuente
Tiene que ver con la forma en que los diferentes compiladores realizan el cambio de nombre. Un compilador de C ++ destrozará el nombre de un símbolo exportado desde el archivo de encabezado de una manera completamente diferente a la de un compilador de C, por lo 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 el cambio de nombre de la misma manera que lo haría el compilador de C. Una vez hecho esto, los errores del enlazador son corregidos.
fuente
C y C ++ tienen reglas diferentes sobre los nombres de los símbolos. Los símbolos son cómo 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 desde un archivo fuente diferente por el mismo (o compatible) compilador. Esto le permite crear un programa con más de un archivo fuente, lo cual es un alivio cuando trabaja en un proyecto grande.
En C la regla es muy simple, los símbolos están todos en un solo espacio de nombre de todos modos. Por lo tanto, 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 lenguajes como C con esta simple regla de denominación de símbolos. Entonces, los símbolos en el enlazador son simples cadenas.
Pero en C ++, el lenguaje le permite tener espacios de nombres y polimorfismo y otras cosas que entran en conflicto con una regla tan simple. Las seis funciones polimórficas llamadas "agregar" deben tener símbolos diferentes, de lo contrario, otros archivos de objeto utilizarán la incorrecta. Esto se hace "destrozando" (es un término técnico) los nombres de los símbolos.
Al vincular el código de C ++ a las bibliotecas o el código de C, necesita una "C" externa escrita en C, como los archivos de encabezado para las bibliotecas de C ++, para decirle al compilador de C ++ que estos nombres de símbolos no deben ser maltratados, mientras que el resto de su código C ++, por supuesto, debe ser destruido o no funcionará.
fuente
Cuando vincula bibliotecas C en archivos de objetos C ++
C y C ++ usan diferentes esquemas para nombrar símbolos. Esto le dice al vinculador que use el esquema de C cuando se vincula en la biblioteca dada.
El uso del esquema de nombres C le permite hacer referencia a símbolos de estilo C. De lo contrario, el enlazador probaría símbolos de estilo C ++ que no funcionarían.
fuente
Debe usar "C" externa siempre que incluya un encabezado que defina funciones que residen en un archivo compilado por un compilador de C, usado en un archivo de C ++. (Muchas bibliotecas C estándar pueden incluir esta verificació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 ambos archivos .c y .cpp se compilan con el compilador de C ++ (g ++, cc, etc.) entonces no es No es realmente necesario, e incluso puede causar errores de enlazador. Si su proceso de compilación usa un compilador de C normal para util.c, entonces necesitará usar "C" externa 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. Todo lo que suele sucederle a una función C es agregar un guión bajo ("_") al comienzo del nombre. Sin usar "C" externa, el enlazador buscará una función llamada DoSomething @@ int @ float () cuando el nombre real de la función es _DoSomething () o simplemente DoSomething ().
El uso de "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 ++.
fuente
El compilador de C ++ crea nombres de símbolos de manera diferente que el compilador de C. Por lo tanto, 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 tienen un aspecto diferente al predeterminado; de lo contrario, el paso del enlace fallará.
fuente
La
extern "C" {}
construcción le indica al compilador que no realice cambios en los nombres declarados entre llaves. Normalmente, el compilador de C ++ "mejora" los nombres de funciones para que codifiquen la información de tipo sobre argumentos y el valor de retorno; Esto se llama el nombre destrozado . losextern "C"
construcción 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 C ++ (desde una DLL, por ejemplo) a clientes C.
fuente
Esto se utiliza para resolver problemas de cambio de nombre. extern C significa que las funciones están en una API de estilo C "plana".
fuente
Descompile un
g++
binario generado para ver qué está pasandoPara 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
Compile con la salida GCC 4.8 Linux ELF :
Descompilar la tabla de símbolos:
La salida contiene:
Interpretación
Vemos eso:
ef
yeg
se almacenaron en símbolos con el mismo nombre que en el códigolos otros símbolos fueron destrozados. Vamos a deshacerlos:
Conclusión: los dos tipos de símbolos siguientes no fueron destrozados:
Ndx = UND
), que se proporcionará en el enlace o tiempo de ejecución desde otro archivo de objetoEntonces necesitará
extern "C"
ambos cuando llame:g++
que espere símbolos sin desencadenar producidos porgcc
g++
que genere símbolos sin desenvolver paragcc
usarCosas 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
: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
ch
cc
Correr:
Sin
extern "C"
el enlace falla con:porque
g++
espera encontrar un destrozadof
, quegcc
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
cpp.h
cpp.cpp
Correr:
Sin
extern "C"
falla con:porque
g++
generó símbolos destrozados quegcc
no pueden encontrar.Ejemplo en GitHub .
Probado en Ubuntu 18.04.
fuente