¿Cuál es el efecto de extern “C” en C ++?

¿Qué es exactamente lo que hace poner extern "C" en el código de C ++?

Por ejemplo:

 extern "C" { void foo(); } 

Extern “C” hace que un nombre de función en C ++ tenga un enlace ‘C’ (el comstackdor no destruye el nombre) para que el código C del cliente pueda vincular (es decir usar) su función usando un archivo de encabezado compatible ‘C’ que contenga solo el statement de tu función. La definición de su función está contenida en un formato binario (comstackdo por su comstackdor de C ++) al que el enlazador del cliente ‘C’ se vinculará con el nombre ‘C’.

Como C ++ tiene una sobrecarga de nombres de funciones y C no, el comstackdor de C ++ no puede usar el nombre de la función como un ID único para vincular, por lo que modifica el nombre al agregar información sobre los argumentos. El comstackdor de AC no necesita modificar el nombre ya que no puede sobrecargar los nombres de las funciones en C. Cuando declara que una función tiene una vinculación externa “C” en C ++, el comstackdor de C ++ no agrega información de tipo de argumento / parámetro al nombre utilizado para enlace.

Para que lo sepas, puedes especificar el enlace “C” a cada statement / definición individual explícitamente o usar un bloque para agrupar una secuencia de declaraciones / definiciones para tener un cierto enlace:

 extern "C" void foo(int); extern "C" { void g(char); int i; } 

Si le interesan los aspectos técnicos, se enumeran en la sección 7.5 del estándar C ++ 03, aquí hay un breve resumen (con énfasis en la opción “C”):

  • Extern “C” es una especificación de enlace
  • Se requiere que cada comstackdor proporcione un enlace “C”
  • una especificación de vinculación debe ocurrir solo en el scope del espacio de nombres
  • todos los tipos de funciones, nombres de funciones y nombres de variables tienen un enlace de lenguaje. Consulte el comentario de Richard: solo los nombres de funciones y variables con enlaces externos tienen un enlace de lenguaje.
  • dos tipos de funciones con enlaces de lenguaje distintos son tipos distintos, incluso si son idénticos
  • nido de especificaciones de vinculación, el interno determina el enlace final
  • Extern “C” se ignora para los miembros de la clase
  • como máximo, una función con un nombre particular puede tener una vinculación “C” (independientemente del espacio de nombres)
  • Extern “C” fuerza a una función a tener un enlace externo (no puede hacer que sea estático) Vea el comentario de Richard: “estático” dentro de “extern” C “‘es válido; una entidad así declarada tiene un enlace interno, por lo que no tiene un enlace de lenguaje
  • La vinculación desde C ++ a objetos definidos en otros lenguajes y a objetos definidos en C ++ desde otros lenguajes está definida por la implementación y depende del idioma. Solo cuando las estrategias de disposición de objetos de dos implementaciones de lenguaje son lo suficientemente similares, se puede lograr tal vinculación

Solo quería agregar un poco de información, ya que no la he visto publicada todavía.

Muy a menudo verá el código en los encabezados C así:

 #ifdef __cplusplus extern "C" { #endif // all of your legacy C code here #ifdef __cplusplus } #endif 

Lo que esto logra es que le permite usar ese archivo de encabezado C con su código C ++, porque se definirá la macro “__cplusplus”. Pero también puede usarlo con su código C heredado, donde la macro NO está definida, por lo que no verá la construcción única de C ++.

Aunque, también he visto código C ++ como:

 extern "C" { #include "legacy_C_header.h" } 

lo cual imagino logra casi lo mismo.

No estoy seguro de qué camino es mejor, pero he visto ambos.

En cada progtwig C ++, todas las funciones no estáticas se representan en el archivo binario como símbolos. Estos símbolos son cadenas de texto especiales que identifican de manera única una función en el progtwig.

En C, el nombre del símbolo es el mismo que el nombre de la función. Esto es posible porque en C no hay dos funciones no estáticas que puedan tener el mismo nombre.

Debido a que C ++ permite la sobrecarga y tiene muchas características que C no tiene -como las clases, las funciones miembro, las especificaciones de excepción- no es posible simplemente usar el nombre de la función como el nombre del símbolo. Para resolver eso, C ++ utiliza el denominado mangling, que transforma el nombre de la función y toda la información necesaria (como el número y tamaño de los argumentos) en una cadena de aspecto extraño procesada solo por el comstackdor y el enlazador.

Por lo tanto, si especifica que una función es externa C, el comstackdor no realiza el cambio de nombre y se puede acceder directamente usando su nombre de símbolo como nombre de la función.

Esto es útil al usar dlsym() y dlopen() para llamar a tales funciones.

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

Entrada:

 void f() {} void g(); extern "C" { void ef() {} void eg(); } /* Prevent g and eg from being optimized away. */ void h() { g(); eg(); } 

Comstackr con GCC 4.8 Linux ELF de salida:

 g++ -c a.cpp 

Descompile la tabla de símbolos:

 readelf -s ao 

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 mutilados. Vamos a desmantelarlos:

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

Conclusión: los dos tipos de símbolos siguientes no se destrozaron:

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

Por lo tanto, necesitará una extern "C" cuando llame:

  • C de C ++: dile a g++ que espere los símbolos no marcados producidos por gcc
  • C ++ desde C: dile a g++ que genere símbolos no marcados para que gcc use

Cosas que no funcionan en el exterior C

Resulta obvio que cualquier característica de C ++ que requiera el cambio de nombre no se ejecutará 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  void f(C i) { } } 

Mínimo ejecutable C desde C ++ ejemplo

Por el bien de la compleción y de los nuevos que hay.

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

main.cpp:

 #include  #include "ch" 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 "ch" int f() { return 1; } 

Correr:

 g++ -c -o main.o -std=c++98 main.cpp gcc -c -o co -std=c89 cc g++ -o main.out main.o co ./main.out 

Sin extern "C" el enlace falla con:

 main.cpp:6: undefined reference to `f()' 

porque g++ espera encontrar un f mutilado, que gcc no produjo.

Ejemplo en GitHub .

Ejemplo mínimo de C ++ ejecutable desde C

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

Aquí ilustramos cómo exponer las sobrecargas de funciones de C ++ a C.

C Principal:

 #include  #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 .

C ++ manipula nombres de funciones para crear un lenguaje orientado a objetos a partir de un lenguaje de procedimientos

La mayoría de los lenguajes de progtwigción no están basados ​​en los lenguajes de progtwigción existentes. C ++ está construido sobre la parte superior de C, y además es un lenguaje de progtwigción orientado a objetos construido a partir de un lenguaje de progtwigción de procedimientos, y por esa razón hay palabras clave de C ++ como extern que proporcionan compatibilidad con C.

Veamos el siguiente ejemplo:

 #include  // Two functions are defined with the same name // but have different parameters void printMe(int a) { printf("int: %i\n", a); } void printMe(char a) { printf("char: %c\n", a); } int main() { printMe("a"); printMe(1); return 0; } 

El comstackdor de CA no comstackrá el ejemplo anterior, porque la misma función printMe se define dos veces (aunque tienen diferentes parámetros int a vs char a ).

gcc -o printMe printMe.c && ./printMe;
1 error PrintMe se define más de una vez.

Un comstackdor de C ++ comstackrá el ejemplo anterior. No importa que printMe esté definido dos veces.

g ++ -o printMe printMe.c && ./printMe;

Esto se debe a que un comstackdor de C ++ renombra implícitamente ( altera ) las funciones en función de sus parámetros. En C, esta función no era compatible. Sin embargo, cuando C ++ se construyó sobre C, el lenguaje fue diseñado para ser orientado a objetos, y necesario para admitir la capacidad de crear diferentes clases con métodos (funciones) del mismo nombre, y para anular métodos ( anulación de método ) en función de diferentes parámetros.

Extern dice “no destroces nombres de funciones”

Sin embargo, imagina que tenemos un archivo C heredado llamado “parent.c” que include nombres de funciones de otros archivos heredados de C, “parent.h”, “child.h”, etc. Si el archivo “parent.c” heredado es ejecutar un comstackdor de C ++, los nombres de las funciones se romperán y ya no coincidirán con los nombres de las funciones especificadas en “parent.h”, “child.h”, etc., por lo que los nombres de las funciones en esos archivos externos necesitarían ser destrozado también. Y esto podría volverse bastante desordenado. Por lo tanto, podría ser conveniente proporcionar una palabra clave que le indique al comstackdor de C ++ que no modifique el nombre de una función.

La palabra clave extern le dice a un comstackdor de C ++ que no modifique (renombre) los nombres de las funciones. Ejemplo de uso: extern void printMe(int a);

Cambia el enlace de una función de tal manera que la función se puede llamar desde C. En la práctica, eso significa que el nombre de la función no se destroza .

No se comstackrá ningún encabezado C con “C” externo. Cuando los identificadores en un encabezado C entran en conflicto con las palabras clave C ++, el comstackdor C ++ se quejará de esto.

Por ejemplo, he visto el siguiente código fallar en un g ++:

 extern "C" { struct method { int virtual; }; } 

Un poco tiene sentido, pero es algo a tener en cuenta cuando se transfiere el código C a C ++.

Informa al comstackdor de C ++ que busque los nombres de esas funciones en un estilo C al vincular, porque los nombres de las funciones comstackdas en C y C ++ son diferentes durante la etapa de vinculación.

Extern “C” está destinado a ser reconocido por un comstackdor de C ++ y para notificar al comstackdor que la función indicada se comstack (o se debe comstackr) en estilo C. De modo que mientras se vincula, se vincula a la versión correcta de la función de C.

Utilicé ‘Extern “C”‘ antes para los archivos dll (dynamic link library) para hacer que la función main () sea “exportable” para que pueda ser usada más tarde en otro ejecutable de dll. Quizás un ejemplo de dónde solía usarlo puede ser útil.

DLL

 #include  #include  using namespace std; #define DLL extern "C" __declspec(dllexport) //I defined DLL for dllexport function DLL main () { MessageBox(NULL,"Hi from DLL","DLL",MB_OK); } 

exe

 #include  #include  using namespace std; typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll Function mainDLLFunc;//make a variable for function placeholder int main() { char winDir[MAX_PATH];//will hold path of above dll GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe strcat(winDir,"\\exmple.dll");//concentrate dll name with path HINSTANCE DLL = LoadLibrary(winDir);//load example dll if(DLL==NULL) { FreeLibrary((HMODULE)DLL);//if load fails exit return 0; } mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main"); //defined variable is used to assign a function from dll //GetProcAddress is used to locate function with pre defined extern name "DLL" //and matcing function name if(mainDLLFunc==NULL) { FreeLibrary((HMODULE)DLL);//if it fails exit return 0; } mainDLLFunc();//run exported function FreeLibrary((HMODULE)DLL); } 

extern "C" es una especificación de enlace que se utiliza para llamar a las funciones C en los archivos fuente Cpp . Podemos llamar a funciones C, escribir variables e incluir encabezados . La función se declara en una entidad externa y se define afuera. La syntax es

Tipo 1:

 extern "language" function-prototype 

Tipo 2:

 extern "language" { function-prototype }; 

p.ej:

 #include using namespace std; extern "C" { #include // Include C Header int n; // Declare a Variable void func(int,int); // Declare a function (function prototype) } int main() { func(int a, int b); // Calling function . . . return 0; } // Function definition . . . void func(int m, int n) { // // } 

Cuando se mezclan C y C ++ (es decir, una llamada a la función C desde C ++ y b) llamar a la función C ++ desde C), el cambio de nombre de C ++ causa problemas de vinculación. Técnicamente hablando, este problema ocurre solo cuando las funciones invocadas ya han sido comstackdas en binario (muy probablemente, un *. Un archivo de biblioteca) usando el comstackdor correspondiente.

Por lo tanto, debemos usar extern “C” para deshabilitar el cambio de nombre en C ++.

Esta respuesta es para los impacientes / tienen plazos para cumplir, solo una parte / explicación simple es la siguiente:

  • en C ++, puede tener el mismo nombre en clase a través de la sobrecarga (por ejemplo, dado que todos son del mismo nombre no se pueden exportar como está de dll, etc.) la solución a estos problemas es que se convierten en diferentes cadenas (llamados símbolos) ), los símbolos representan el nombre de la función, también los argumentos, de modo que cada una de estas funciones, incluso con el mismo nombre, puede identificarse de manera única (también llamado, mangle del nombre)
  • en C, no tiene sobrecarga, el nombre de la función es único (por lo tanto, no se requiere una cadena separada para identificar el nombre de una función de manera única, por lo que el símbolo es el nombre de la función en sí)

Asi que
en C ++, con nombre que destruye identidades únicas cada función
en C, incluso sin nombre manipulando identidades únicas cada función

Para cambiar el comportamiento de C ++, es decir, para especificar que no se debe cambiar el nombre para una función en particular, puede usar extern “C” antes del nombre de la función, por cualquier razón, como exportar una función con un nombre específico de un dll , para uso de sus clientes.

Lea otras respuestas para obtener respuestas más detalladas / más correctas.