Pergunta

Por que precisamos usar:

extern "C" {
#include <foo.h>
}

Especificamente:

  • Quando devemos usá-lo?

  • O que está acontecendo no nível do compilador/vinculador que exige que o utilizemos?

  • Como, em termos de compilação/vinculação, isso resolve os problemas que exigem seu uso?

Foi útil?

Solução

C e C++ são superficialmente semelhantes, mas cada um é compilado em um conjunto de código muito diferente.Quando você inclui um arquivo de cabeçalho com um compilador C++, o compilador espera código C++.Se, no entanto, for um cabeçalho C, o compilador espera que os dados contidos no arquivo de cabeçalho sejam compilados em um determinado formato - o 'ABI' do C++ ou 'Interface Binária do Aplicativo', de modo que o vinculador engasga.Isso é preferível a passar dados C++ para uma função que espera dados C.

(Para entrar no âmago da questão, a ABI do C++ geralmente 'mutila' os nomes de suas funções/métodos, chamando printf() sem sinalizar o protótipo como uma função C, o C++ irá realmente gerar chamada de código _Zprintf, além de porcaria extra no final.)

Então:usar extern "C" {...} ao incluir um cabeçalho c - é simples assim.Caso contrário, você terá uma incompatibilidade no código compilado e o vinculador irá engasgar.Para a maioria dos cabeçalhos, entretanto, você nem precisará do extern porque a maioria dos cabeçalhos do sistema C já leva em conta o fato de que eles podem ser incluídos pelo código C++ e já extern seu código.

Outras dicas

extern "C" determina como os símbolos no arquivo objeto gerado devem ser nomeados.Se uma função for declarada sem "C" externo, o nome do símbolo no arquivo objeto usará a manipulação de nome C++.Aqui está um exemplo.

Dado test.C assim:

void foo() { }

Compilar e listar símbolos no arquivo objeto fornece:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

A função foo é na verdade chamada de "_Z3foov".Essa string contém informações de tipo para o tipo de retorno e parâmetros, entre outras coisas.Se você escrever test.C assim:

extern "C" {
    void foo() { }
}

Em seguida, compile e observe os símbolos:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

Você obtém ligação C.O nome da função "foo" no arquivo objeto é apenas "foo" e não contém todas as informações sofisticadas de tipo que vêm da manipulação de nomes.

Geralmente, você inclui um cabeçalho em extern "C" {} se o código que o acompanha foi compilado com um compilador C, mas você está tentando chamá-lo de C++.Ao fazer isso, você está informando ao compilador que todas as declarações no cabeçalho usarão ligação C.Quando você vincula seu código, seus arquivos .o conterão referências a "foo", não a "_Z3fooblah", que, esperançosamente, corresponde ao que está na biblioteca à qual você está vinculando.

A maioria das bibliotecas modernas colocará guardas em torno desses cabeçalhos para que os símbolos sejam declarados com a ligação correta.por exemplo.em muitos dos cabeçalhos padrão você encontrará:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Isso garante que, quando o código C++ incluir o cabeçalho, os símbolos no seu arquivo de objeto correspondam ao que está na biblioteca C.Você só deve colocar extern "C" {} em torno do cabeçalho C se ele for antigo e ainda não tiver essas proteções.

Em C++, você pode ter diferentes entidades que compartilham um nome.Por exemplo, aqui está uma lista de funções todas nomeadas foo:

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

Para diferenciar todos eles, o compilador C++ criará nomes exclusivos para cada um em um processo chamado manipulação de nomes ou decoração.Compiladores C não fazem isso.Além disso, cada compilador C++ pode fazer isso de uma maneira diferente.

extern "C" diz ao compilador C++ para não executar qualquer manipulação de nomes no código entre colchetes.Isso permite que você chame funções C de dentro do C++.

Tem a ver com a maneira como os diferentes compiladores executam a manipulação de nomes.Um compilador C++ irá alterar o nome de um símbolo exportado do arquivo de cabeçalho de uma maneira completamente diferente do que um compilador C faria, portanto, quando você tentar vincular, você receberá um erro de vinculador informando que faltam símbolos.

Para resolver isso, dizemos ao compilador C++ para executar no modo "C", para que ele execute a manipulação de nomes da mesma forma que o compilador C faria.Feito isso, os erros do vinculador são corrigidos.

Quando devemos usá-lo?

Quando você está vinculando bibliotecas C em arquivos de objeto C++

O que está acontecendo no nível do compilador/ligante que exige que o usemos?

C e C++ usam esquemas diferentes para nomenclatura de símbolos.Isso diz ao vinculador para usar o esquema C ao vincular em uma determinada biblioteca.

Como, em termos de compilação/vinculação, isso resolve os problemas que exigem que o usemos?

Usar o esquema de nomenclatura C permite fazer referência a símbolos de estilo C.Caso contrário, o vinculador tentaria símbolos no estilo C++ que não funcionariam.

C e C++ têm regras diferentes sobre nomes de símbolos.Símbolos são como o vinculador sabe que a chamada para a função "openBankAccount" em um arquivo objeto produzido pelo compilador é uma referência àquela função que você chamou de "openBankAccount" em outro arquivo objeto produzido a partir de um arquivo de origem diferente pelo mesmo (ou compatível) compilador.Isso permite que você crie um programa a partir de mais de um arquivo fonte, o que é um alívio ao trabalhar em um projeto grande.

Em C, a regra é muito simples: de qualquer maneira, os símbolos estão todos em um único espaço de nomes.Portanto, o inteiro "socks" é armazenado como "socks" e a função count_socks é armazenada como "count_socks".

Linkers foram construídos para C e outras linguagens como C com esta regra simples de nomenclatura de símbolos.Portanto, os símbolos no vinculador são apenas strings simples.

Mas em C++ a linguagem permite que você tenha namespaces, polimorfismo e várias outras coisas que entram em conflito com uma regra tão simples.Todas as seis funções polimórficas chamadas "add" precisam ter símbolos diferentes, ou o errado será usado por outros arquivos de objeto.Isso é feito "mutilando" (este é um termo técnico) os nomes dos símbolos.

Ao vincular o código C++ a bibliotecas ou código C, você precisa de "C" externo, qualquer coisa escrita em C, como arquivos de cabeçalho para as bibliotecas C, para informar ao seu compilador C++ que esses nomes de símbolos não devem ser mutilados, enquanto o resto do é claro que seu código C++ deve ser mutilado ou não funcionará.

Você deve usar extern "C" sempre que incluir um cabeçalho que defina funções residentes em um arquivo compilado por um compilador C, usado em um arquivo C++.(Muitas bibliotecas C padrão podem incluir esta verificação em seus cabeçalhos para torná-la mais simples para o desenvolvedor)

Por exemplo, se você tiver um projeto com 3 arquivos, util.c, util.h e main.cpp e ambos os arquivos .c e .cpp forem compilados com o compilador C++ (g++, cc, etc), então não é realmente necessário e pode até causar erros no vinculador.Se o seu processo de construção usa um compilador C regular para util.c, você precisará usar "C" externo ao incluir util.h.

O que está acontecendo é que C++ codifica os parâmetros da função em seu nome.É assim que funciona a sobrecarga de funções.Tudo o que tende a acontecer com uma função C é a adição de um sublinhado (“_”) ao início do nome.Sem usar extern "C", o vinculador procurará uma função chamada DoSomething@@int@float() quando o nome real da função for _DoSomething() ou apenas DoSomething().

Usar extern "C" resolve o problema acima, informando ao compilador C++ que ele deve procurar uma função que siga a convenção de nomenclatura C em vez da convenção C++.

O compilador C++ cria nomes de símbolos de maneira diferente do compilador C.Portanto, se você estiver tentando fazer uma chamada para uma função que reside em um arquivo C, compilado como código C, será necessário informar ao compilador C++ que os nomes dos símbolos que ele está tentando resolver parecem diferentes do padrão;caso contrário, a etapa de link falhará.

O extern "C" {} construct instrui o compilador a não executar confusão em nomes declarados entre colchetes.Normalmente, o compilador C++ "aprimora" os nomes das funções para que codifiquem informações de tipo sobre argumentos e o valor de retorno;isso é chamado de nome mutilado.O extern "C" construção evita a confusão.

Normalmente é usado quando o código C++ precisa chamar uma biblioteca da linguagem C.Também pode ser usado ao expor uma função C++ (de uma DLL, por exemplo) para clientes C.

Isso é usado para resolver problemas de confusão de nomes.extern C significa que as funções estão em uma API "simples" no estilo C.

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

Estou mudando esta resposta de: Qual é o efeito do "C" externo em C++? já que essa pergunta foi considerada uma duplicata desta.

principal.cpp

void f() {}
void g();

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

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

Compilar com GCC 4.8 Linux DUENDE saída:

g++ -c main.cpp

Descompile a tabela de símbolos:

readelf -s main.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 do código

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

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

Conclusão:ambos os seguintes tipos de símbolos foram não mutilado:

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

Então você vai precisar extern "C" ambos ao ligar:

  • C de C++:dizer g++ esperar símbolos não mutilados produzidos por gcc
  • C++ de C:dizer g++ para gerar símbolos não mutilados para gcc usar

Coisas que não funcionam em C externo

Torna-se óbvio que qualquer recurso C++ que exija a manipulação de nomes não funcionará dentro 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 executável a partir de exemplo C++

Para completar e para os novatos, veja também: Como usar arquivos de origem C em um projeto C++?

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

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

Sem extern "C" o link falha com:

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

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

Exemplo no GitHub.

C++ executável mínimo 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 ilustramos como expor sobrecargas de funções C++ para C.

principal.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);
}

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

Sem extern "C" falha com:

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

porque g++ gerou símbolos mutilados que gcc não consigo encontrar.

Exemplo no GitHub.

Testado no Ubuntu 18.04.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top