Pregunta

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

Por ejemplo:

extern "C" {
   void foo();
}
¿Fue útil?

Solución

externo '' C '' hace que un nombre de función en C ++ tenga un enlace 'C' (el compilador no daña el nombre) para que el código C del cliente pueda enlazar (es decir, usar) su función usando un archivo de encabezado compatible con 'C' que contenga solo la 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 enlazador 'C' del cliente vinculará utilizando el nombre 'C'.

Dado que C ++ tiene una sobrecarga de nombres de funciones y C no, el compilador de C ++ no puede simplemente usar el nombre de la función como una identificación única para vincular, por lo que desbarata el nombre agregando información sobre los argumentos. Un compilador de C no necesita alterar el nombre ya que no puede sobrecargar los nombres de las funciones en C. Cuando declara que una función tiene "C" externa. enlace en C ++, el compilador de C ++ no agrega información de tipo argumento / parámetro al nombre utilizado para el enlace.

Para que lo sepas, puedes especificar " C " enlace a cada declaración / definición individual explícitamente o use 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 tecnicismos, se enumeran en la sección 7.5 del estándar C ++ 03, aquí hay un breve resumen (con énfasis en "C" externo):

  • externo '' C '' es una especificación de enlace
  • Todos los compiladores son necesarios para proporcionar " C " enlace
  • una especificación de vinculación solo ocurrirá en el ámbito del espacio de nombres
  • todos los tipos de funciones, nombres de funciones y nombres de variables tienen un enlace de idioma Vea el comentario de Richard: Solo los nombres de funciones y variables con enlace externo tienen un enlace de idioma
  • dos tipos de funciones con enlaces de idiomas distintos son tipos distintos, incluso si son idénticos
  • las especificaciones de vinculación anidan, la interna determina la vinculación final
  • externo '' C '' se ignora para los miembros de la clase
  • como máximo una función con un nombre particular puede tener " C " enlace (independientemente del espacio de nombres)
  • extern " C " obliga a una función a tener un enlace externo (no puede hacerla estática) Ver el comentario de Richard: 'estático' dentro de 'extern' 'C' ' es válida; una entidad así declarada tiene un enlace interno y, por lo tanto, no tiene un enlace de idioma
  • La vinculación de C ++ a objetos definidos en otros lenguajes y a objetos definidos en C ++ de otros lenguajes está definida por la implementación y depende del lenguaje. Solo cuando las estrategias de diseño de objetos de las implementaciones de dos idiomas son lo suficientemente similares, se puede lograr dicha vinculación

Otros consejos

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

Muy a menudo verá código en encabezados C de esta manera:

#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 la macro " __ cplusplus " será definido Pero puede también seguir usándolo con su código C heredado, donde la macro está NO definida, por lo que no verá la construcción exclusiva de C ++.

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

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

que imagino logra casi lo mismo.

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

En cada programa 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 exclusiva 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 clases, funciones miembro, especificaciones de excepción, no es posible usar simplemente el nombre de la función como el nombre del símbolo. Para resolver eso, C ++ usa el llamado cambio de nombre, 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 enlazador.

Entonces, si especifica que una función sea externa C, el compilador no realiza cambios de nombre con ella y puede ser directamente accedido utilizando su nombre de símbolo como el nombre de la función.

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

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

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

g++ -c main.cpp

Descompilar la tabla de símbolos:

readelf -s main.o

El resultado 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 que:

  • ef y por ejemplo se almacenaron en símbolos con el mismo nombre que en el código

  • los otros símbolos fueron destrozados. Vamos a deshacerlos:

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

Conclusión: los dos tipos de símbolos siguientes fueron no destrozados:

  • 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á extern " C " ambos cuando llame:

  • C de C ++: dígale a g ++ que espere símbolos sin desencadenar producidos por gcc
  • C ++ desde C: dígale a g ++ que genere símbolos no desencadenados para que gcc use

Cosas que no funcionan en C externo

Resulta obvio que cualquier característica de C ++ que requiera el cambio de nombre 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) { }
}

Ejemplo mínimo de C ejecutable desde C ++

En aras de la integridad 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

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

Ejecutar:

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 un f destrozado, que gcc no produjo.

Ejemplo en GitHub .

Ejemplo de C ++ ejecutable mínimo desde C

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

Aquí ilustramos cómo exponer sobrecargas de la función C ++ a C.

main.c

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

Ejecutar:

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 .

Probado en Ubuntu 18.04.

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

La mayoría de los lenguajes de programación no se crean sobre los lenguajes de programación existentes. C ++ está construido sobre C, y además es un lenguaje de programación orientado a objetos construido a partir de un lenguaje de programación procesal, y por esa razón hay expresiones C ++ como extern " C " que proporcionan compatibilidad con versiones anteriores 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 (a pesar de que tienen diferentes parámetros int a vs char a ).

  

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

Un compilador de C ++ compilará el ejemplo anterior. No le importa que printMe se defina dos veces.

  

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

Esto se debe a que un compilador de C ++ renombra implícitamente ( mangles ) funciones basadas en sus parámetros. En C, esta característica no era compatible. Sin embargo, cuando C ++ se creó sobre C, el lenguaje se diseñó para estar orientado a objetos y necesitaba admitir la capacidad de crear diferentes clases con métodos (funciones) del mismo nombre, y anular métodos ( anulación de método ) en función de diferentes parámetros.

extern " C " dice " no altere los nombres de las funciones C "

Sin embargo, imagine que tenemos un archivo C heredado llamado '' parent.c '' que incluyen los nombres de funciones de otros archivos C heredados, "parent.h", "child.h", etc. Si el heredado "parent.c" el archivo se ejecuta a través de un compilador de C ++, luego los nombres de las funciones se alterará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 externos los archivos también tendrían que ser destrozados. El cambio de nombres de funciones en un programa C complejo, aquellos con muchas dependencias, puede conducir a un código roto; Por lo tanto, puede ser conveniente proporcionar una palabra clave que pueda indicarle al compilador de C ++ que no altere el nombre de una función.

La palabra clave extern " C " le dice a un compilador de C ++ que no altere (renombre) los nombres de las funciones de C. Ejemplo de uso: extern " C " vacío 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 es destrozado .

No se puede hacer que ningún encabezado C sea compatible con C ++ simplemente envolviendo en "C" externo. 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;
};
}

Tiene un poco de sentido, pero es algo a tener en cuenta al portar código C a C ++.

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

externo '' C '' está destinado a ser reconocido por un compilador de C ++ y notificar al compilador que la función anotada se compila (o se compilará) en estilo C. Para que al vincular, se vincule a la versión correcta de la función de C.

Usé 'extern' 'C' ' antes para que los archivos dll (biblioteca de enlaces dinámicos) hagan que la función main () "exportable", etc. para que pueda usarse más tarde en otro ejecutable desde dll. Quizás un ejemplo de dónde 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 " es una especificación de enlace que se utiliza para llamar a funciones C en los archivos fuente de Cpp . Podemos llamar a funciones C, escribir variables, & amp; incluir encabezados . La función se declara en la entidad externa & amp; se define afuera. La sintaxis es

Tipo 1:

extern "language" function-prototype

Tipo 2:

extern "language"
{
     function-prototype
};

eg:

#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)
{
    //
    //
}

Esta respuesta es para los impacientes / tienen plazos para cumplir, solo una parte / explicación simple está a continuación:

  • en C ++, puede tener el mismo nombre en clase mediante sobrecarga (por ejemplo, dado que todos tienen el mismo nombre, no se pueden exportar tal cual desde dll, etc.) la solución a estos problemas es que se convierten en cadenas diferentes (llamados 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 manera única (también llamada, cambio de 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í)

Entonces
en C ++, con el cambio de nombre identifica de forma única cada función
en C, incluso sin el cambio de nombre identifica de forma única cada función

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

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

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), el cambio de nombre de C ++ causa problemas de vinculación. Técnicamente hablando, este problema ocurre solo cuando las funciones de la persona que llama ya se han compilado en binario (muy probablemente, un archivo de biblioteca * .a) usando el compilador correspondiente.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top