variable usar significado que initialize hace example como c++ c linkage name-mangling extern-c

c++ - usar - que hace extern en c



¿Cuál es el efecto de extern "C" en C++? (13)

Nombres de funciones de C ++ Mangles para crear un lenguaje orientado a objetos a partir de un lenguaje de procedimiento

La mayoría de los lenguajes de programación no están construidos sobre los lenguajes de programación existentes. C ++ está construido sobre C, y además es un lenguaje de programación orientado a objetos creado a partir de un lenguaje de programación de procedimientos, y por esa razón hay palabras clave de C ++ como extern que proporcionan compatibilidad hacia atrás con C.

Veamos el siguiente ejemplo:

#include <stdio.h> // 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 compilador de CA no compilará el ejemplo anterior, porque la misma función printMe se define dos veces (aunque tienen parámetros diferentes int a vs char a ).

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

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

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

Esto se debe a que un compilador de C ++ renombra implícitamente las funciones ( mangles ) en función de sus parámetros. En C, esta característica no era compatible. Sin embargo, cuando C ++ se construyó sobre C, el lenguaje se diseñó para estar orientado a objetos, y era 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étodos) basados ​​en diferentes parámetros

Extern dice "no mangle nombres de funciones"

Sin embargo, imagine que tenemos un archivo C heredado llamado "parent.c" que include nombres de funciones de otros archivos C heredados, "parent.h", "child.h", etc. Si el archivo heredado "parent.c" es ejecutar un compilador de C ++, luego los nombres de las funciones serán modificados 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 también necesitarán ser destrozado La manipulación de nombres de funciones en un programa complejo de C, aquellos con muchas dependencias, puede llevar a código roto; por lo que podría ser conveniente proporcionar una palabra clave que le diga al compilador de C ++ que no destruya el nombre de una función.

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

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

Por ejemplo:

extern "C" { void foo(); }


Al mezclar C y C ++ (es decir, a. Llamar a la función C desde C ++; y b. Llamar a la función C ++ desde C), la manipulación de nombres en C ++ causa problemas de enlace. Hablando técnicamente, este problema ocurre solo cuando las funciones del destinatario ya se han compilado en binario (lo más probable, un archivo de biblioteca * .a) utilizando el compilador correspondiente.

Por lo tanto, necesitamos usar extern "C" para deshabilitar la mutilación de nombres en C ++.


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 está mangled .


En cada programa de 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 forma única una función en el programa.

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 usar el nombre de la función como el nombre del símbolo. Para resolver eso, C ++ utiliza la llamada "denominación de nombres", que transforma el nombre de la función y toda la información necesaria (como el número y el tamaño de los argumentos) en una cadena de aspecto extraño procesada solo por el compilador y el vinculador.

Entonces, si especifica que una función sea externa C, el compilador no realiza la manipulación de nombres con ella y se puede acceder directamente usando su nombre de símbolo como el nombre de la función.

Esto es útil cuando se usan dlsym() y dlopen() para llamar a tales funciones.


Esta respuesta es para los impacientes / tienen fechas límite para cumplir, solo una parte / explicación simple a continuación:

  • en C ++, puede tener el mismo nombre en la clase a través de la sobrecarga (por ejemplo, dado que todos son del mismo nombre no se pueden exportar como están desde dll, etc.). La solución a estos problemas es que se convierten en diferentes cadenas (denominadas símbolos). ), los símbolos representan el nombre de la función, también los argumentos, por lo que cada una de estas funciones, incluso con el mismo nombre, puede identificarse de forma única (también llamada, denominación de nombres)
  • 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 la 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 denominación de nombres identifica de forma única cada función
en C, incluso sin el nombre de las identidades, identifica individualmente cada función

Para cambiar el comportamiento de C ++, es decir, para especificar que la mutilación de nombres no debería ocurrir para una función en particular, puede usar extern "C" antes del nombre de la función, por cualquier motivo, como exportar una función con un nombre específico desde una dll , para uso de sus clientes.

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


Le informa al compilador de C ++ que busque los nombres de esas funciones en un estilo de C al vincular, porque los nombres de las funciones compiladas en C y C ++ son diferentes durante la etapa de vinculación.


Ningún encabezado C se compilará con extern "C". Cuando los identificadores en un encabezado C entran en conflicto con las palabras clave de C ++, el compilador de C ++ se quejará de esto.

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

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

Algo tiene sentido, pero es algo que se debe tener en cuenta al portar el código C a C ++.


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

Muy a menudo verás el código en los encabezados de 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 puedes usarlo con tu 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 que imagino logra mucho lo mismo.

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


Utilicé ''extern "C"'' antes para los archivos dll (biblioteca de enlace dinámico) para hacer que la función main (), etc., sea "exportable", por lo que se puede usar más tarde en otro ejecutable de dll. Tal vez un ejemplo de donde solía usarlo puede ser útil.

DLL

#include <string.h> #include <windows.h> 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 <string.h> #include <windows.h> 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" está destinado a ser reconocido por un compilador de C ++ y notificar al compilador que la función anotada se compila (o se debe) compilar en estilo C. De modo que, al enlazar, se vincula a la versión correcta de la función desde C.


extern "C" hace que un nombre de función en C ++ tenga un enlace ''C'' (el compilador no modifica el nombre) para que el código C del cliente pueda vincularse (es decir, usar) su función usando un archivo de encabezado compatible con ''C'' que contenga solo el Declaración de su función. La definición de su función está contenida en un formato binario (que fue compilado por su compilador de C ++) que el vinculador ''C'' del cliente vinculará para usar el nombre ''C''.

Como C ++ tiene una sobrecarga de nombres de funciones y C no, el compilador de C ++ no puede usar el nombre de la función como un ID único para vincular, por lo que maneja el nombre agregando información sobre los argumentos. El compilador de CA no necesita modificar el nombre, ya que no puede sobrecargar los nombres de las funciones en C. Cuando se indica que una función tiene un enlace externo "C" en C ++, el compilador de C ++ no agrega información de tipo de parámetro / parámetro al nombre utilizado para enlace.

Para que sepa, puede especificar el enlace "C" para cada declaración / definición individual explícitamente o usar un bloque para agrupar una secuencia de declaraciones / definiciones para tener un enlace determinado:

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 de la norma C ++ 03, aquí hay un breve resumen (con énfasis en extern "C"):

  • extern "C" es una especificación de vinculación
  • Cada compilador es requerido para proporcionar el enlace "C"
  • una especificación de vinculación se producirá solo en el ámbito del espacio de nombres
  • todos los tipos de funciones, nombres de funciones y nombres de variables tienen una vinculación de idioma Vea el Comentario de Richard: Solo los nombres de funciones y variables con enlace externo tienen una vinculación de idioma
  • dos tipos de funciones con vínculos de idiomas distintos son tipos distintos, incluso si, de lo contrario, son idénticos
  • Las especificaciones de vinculación se anidan, la interna determina la vinculación final.
  • extern "C" se ignora para los miembros de la clase
  • a lo sumo, una función con un nombre particular puede tener un enlace "C" (independientemente del espacio de nombres)
  • extern "C" obliga a una función a tener un enlace externo (no puede hacerlo estático) Vea el comentario de Richard: ''static'' inside ''extern "C"'' es válido; una entidad así declarada tiene enlace interno, y por lo tanto no tiene un enlace de idioma
  • La vinculación de C ++ a los objetos definidos en otros idiomas y a los objetos definidos en C ++ desde otros idiomas está definida por la implementación y depende del idioma. Solo cuando las estrategias de diseño de objetos de dos implementaciones de lenguaje son lo suficientemente similares, se puede lograr tal vinculación

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

Tipo 1:

extern "language" function-prototype

Tipo 2:

extern "language" { function-prototype };

p.ej:

#include<iostream> using namespace std; extern "C" { #include<stdio.h> // 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) { // // }


Descompilar un binario generado en 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(); }

Compile con salida GCC 4.8 Linux ELF:

g++ -c a.cpp

Descompilar la tabla de símbolos:

readelf -s a.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: los dos tipos de símbolos siguientes no fueron destrozados:

  • 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 una extern "C" al llamar:

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

Cosas que no funcionan en C externo

Se vuelve obvio que cualquier característica de C ++ que requiera la manipulación de nombres no se activará 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

Por el bien de la integridad y por las noticias que hay por ahí.

Llamar a C desde C ++ es bastante fácil: 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); }

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

Ejemplo mínimo ejecutable de C ++ de C

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 destrozados que gcc no puede encontrar.

Ejemplo en GitHub .