c++ - language - visual studio extern c




Qual é o efeito do extern “C” em C++? (9)

O que exatamente faz colocar extern "C" em código C ++?

Por exemplo:

extern "C" {
   void foo();
}

C ++ manipula nomes de funções para criar uma linguagem orientada a objetos a partir de uma linguagem procedural

A maioria das linguagens de programação não é construída sobre as linguagens de programação existentes. C ++ é construído no topo de C, e além disso é uma linguagem de programação orientada a objetos construída a partir de uma linguagem de programação procedural, e por essa razão existem palavras-chave C ++ como extern que fornecem compatibilidade retroativa com C.

Vamos ver o seguinte exemplo:

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

O compilador AC não compilará o exemplo acima, porque a mesma função printMe é definida duas vezes (mesmo que tenham parâmetros diferentes int a char a ).

gcc -o printMe printMe.c && ./printMe;
1 erro. O PrintMe é definido mais de uma vez.

Um compilador C ++ irá compilar o exemplo acima. Não importa que printMe seja definido duas vezes.

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

Isso ocorre porque um compilador C ++ renomeia implicitamente ( mangles ) funções com base em seus parâmetros. Em C, esse recurso não era suportado. No entanto, quando o C ++ foi construído sobre C, a linguagem foi projetada para ser orientada a objetos e precisava suportar a capacidade de criar classes diferentes com métodos (funções) de mesmo nome e substituir métodos ( substituição de método ) com base em diferentes parâmetros.

Extern diz "não mangle nomes de função"

Entretanto, imagine que temos um arquivo C legado chamado "parent.c" que include nomes das funções de outros arquivos C legados, "parent.h", "child.h", etc. Se o arquivo legado "parent.c" é executado através de um compilador C ++, então os nomes das funções serão desconfigurados e eles não corresponderão mais aos nomes das funções especificados em "parent.h", "child.h", etc - então os nomes das funções nesses arquivos externos também precisariam ser mutilado. Manipular nomes de funções em um programa C complexo, com muitas dependências, pode levar a códigos quebrados; Por isso, pode ser conveniente fornecer uma palavra-chave que possa dizer ao compilador C ++ para não manipular um nome de função.

A palavra-chave extern informa um compilador C ++ para não manejar (renomear) nomes de função. Exemplo de uso: extern void printMe(int a);


Ao misturar C e C ++ (ou seja, uma função. Chamando C de C ++; e chamando a função C ++ de C), o nome do C ++ mangling causa problemas de vinculação. Tecnicamente falando, esse problema ocorre somente quando as funções recebidas já foram compiladas em binário (provavelmente, um arquivo de biblioteca * .a) usando o compilador correspondente.

Portanto, precisamos usar o "C" externo para desativar o nome mangling em C ++.


Ele informa o compilador C ++ para procurar os nomes dessas funções em um estilo C ao vincular, porque os nomes das funções compiladas em C e C ++ são diferentes durante o estágio de vinculação.


Em todo programa C ++, todas as funções não-estáticas são representadas no arquivo binário como símbolos. Esses símbolos são strings de texto especiais que identificam exclusivamente uma função no programa.

Em C, o nome do símbolo é o mesmo que o nome da função. Isso é possível porque em C não há duas funções não estáticas que possam ter o mesmo nome.

Como o C ++ permite sobrecarga e possui muitos recursos que o C não possui - como classes, funções de membro, especificações de exceção - não é possível simplesmente usar o nome da função como o nome do símbolo. Para resolver isso, o C ++ usa o chamado nome mangling, que transforma o nome da função e todas as informações necessárias (como o número e o tamanho dos argumentos) em alguma string de aparência estranha processada apenas pelo compilador e pelo vinculador.

Portanto, se você especificar uma função para ser extern C, o compilador não executará o mangling de nomes e poderá ser acessado diretamente usando seu nome de símbolo como o nome da função.

Isso é útil ao usar dlsym() e dlopen() para chamar tais funções.


Eu usei "extern" C "'antes de arquivos dll (biblioteca de vínculo dinâmico) para fazer etc. main () função" exportable "para que possa ser usado mais tarde em outro executável de dll. Talvez um exemplo de onde eu costumava usá-lo pode 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);
}

Nenhum cabeçalho C será compilado com o "C" externo. Quando identificadores em um conflito C-header com palavras-chave C ++, o compilador C ++ irá reclamar sobre isso.

Por exemplo, vi o seguinte código falhar em um g + +:

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

Meio que faz sentido, mas é algo para se ter em mente ao portar códigos C para C ++.


extern "C" deve ser reconhecido por um compilador C ++ e notificar o compilador que a função anotada é (ou ser) compilada no estilo C. Para que, ao vincular, vincule a versão correta da função de C.


extern "C" faz um nome de função em C ++ ter um enlace 'C' (o compilador não mangle o nome) para que o código C do cliente possa vincular (isto é, usar) sua função usando um arquivo de cabeçalho compatível com 'C' que contém apenas o declaração da sua função. Sua definição de função está contida em um formato binário (que foi compilado pelo seu compilador C ++) que o vinculador 'C' do cliente irá vincular usando o nome 'C'.

Como C ++ tem sobrecarga de nomes de funções e C não, o compilador C ++ não pode usar apenas o nome da função como um ID exclusivo para vincular, portanto, ele manipula o nome adicionando informações sobre os argumentos. O compilador AC não precisa manipular o nome, pois você não pode sobrecarregar nomes de função em C. Quando você declara que uma função possui uma ligação externa "C" em C ++, o compilador C ++ não adiciona informações de tipo argumento / parâmetro ao nome usado para ligação.

Apenas para você saber, você pode especificar a ligação "C" para cada declaração / definição individual explicitamente ou usar um bloco para agrupar uma sequência de declarações / definições para ter uma certa ligação:

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

Se você se preocupa com os aspectos técnicos, eles estão listados na seção 7.5 do padrão C ++ 03, aqui está um breve resumo (com ênfase no externo "C"):

  • extern "C" é uma especificação de ligação
  • Todo compilador é obrigado a fornecer ligação "C"
  • uma especificação de ligação deve ocorrer apenas no escopo do namespace
  • Todos os tipos de funções, nomes de funções e nomes de variáveis ​​têm uma ligação de linguagem. Veja Comentário de Richard: Somente nomes de funções e nomes de variáveis ​​com ligações externas têm uma ligação de linguagem
  • dois tipos de funções com ligações de linguagem distintas são tipos distintos, mesmo que idênticos
  • especificações de ligação ninho, interior determina a ligação final
  • extern "C" é ignorado para os alunos
  • no máximo, uma função com um nome específico pode ter uma ligação "C" (independentemente do espaço de nomes)
  • extern "C" força uma função a ter ligação externa (não pode torná-la estática) Veja o comentário de Richard: 'static' inside 'extern' C '' é válido; uma entidade assim declarada tem ligação interna e, portanto, não tem uma ligação de linguagem
  • A ligação de C ++ a objetos definidos em outras linguagens e a objetos definidos em C ++ de outras linguagens é definida pela implementação e depende do idioma. Somente quando as estratégias de layout de objeto de duas implementações de linguagem são semelhantes o suficiente, pode-se obter essa vinculação

Decifile um binário gerado por g++ para ver o que está acontecendo

Entrada:

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Compile com a saída GCC 4.8 Linux ELF:

g++ -c a.cpp

Decompile a tabela de símbolos:

readelf -s a.o

A saída contém:

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

Interpretação

Nós vemos que:

  • ef e, eg foram armazenados em símbolos com o mesmo nome que no código

  • os outros símbolos foram mutilados. Vamos soltá-los:

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

Conclusão: os dois tipos de símbolos a seguir não foram desconfigurados:

  • definiram
  • declarado mas indefinido ( Ndx = UND ), a ser fornecido no link ou no tempo de execução de outro arquivo de objeto

Então você precisará do extern "C" ao chamar:

  • C de C ++: diga ao g++ para esperar símbolos não manipulados produzidos pelo gcc
  • C ++ de C: diga g++ para gerar símbolos não manipulados para o gcc usar

Coisas que não funcionam no extern C

Torna-se óbvio que qualquer recurso de C ++ que requer mangling de nomes não será feito dentro do 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) { }
}

Minimal runnable C from C ++ example

Por uma questão de completude e para os newbs lá fora.

Chamar C de C ++ é muito fácil: cada função C tem apenas um símbolo não mutilado possível, portanto, nenhum trabalho extra é necessário.

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

Corre:

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

Sem o extern "C" o link falha com:

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

porque o g++ espera encontrar um f desconfigurado, o qual o gcc não produziu.

Exemplo no GitHub .

Mínimo runnable C ++ do exemplo C

Chamar C ++ é um pouco mais difícil: temos que criar manualmente versões não mutiladas de cada função que queremos expor.

Aqui nós ilustramos como expor sobrecargas de função C ++ para 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);
}

Corre:

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

Sem o extern "C" ele falha com:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

porque g++ gerou símbolos desconfigurados que o gcc não pode encontrar.

Exemplo no GitHub .





extern-c