it-swarm-pt.tech

Por que precisamos de um "C" externo {#include <foo.h>} em C ++?

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 usemos?

  • Como, em termos de compilação/vinculação, isso resolve os problemas que exigem que nós a usemos?

129
Landon

C e C++ são superficialmente similares, mas cada um compila em um conjunto muito diferente de código. Quando você inclui um arquivo de cabeçalho com um compilador C++, o compilador espera o 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 para um determinado formato - o C++ 'ABI' ou 'Application Binary Interface', para que o vinculador seja ativado. É preferível passar dados C++ para uma função que espera dados C.

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

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

117
duane

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

Dado test.C assim:

void foo() { }

Compilar e listar símbolos no arquivo de objeto fornece:

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

A função foo é realmente chamada "_Z3foov". Essa sequência 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ê recebe ligação C. O nome da função "foo" no arquivo de objeto é apenas "foo", e não possui todas as informações de tipo sofisticadas provenientes do mangling de nome.

Você geralmente inclui um cabeçalho no extern "C" {} se o código que o acompanha foi compilado com um compilador C, mas você está tentando chamá-lo a partir do C++. Ao fazer isso, você está dizendo ao compilador que todas as declarações no cabeçalho usarão a ligação C. Quando você vincula seu código, seus arquivos .o contêm referências a "foo", não a _Z3fooblah ", que, esperançosamente, corresponde ao que estiver 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 cabeçalhos padrão, você encontrará:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Isso garante que, quando o código C++ inclua 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 volta do cabeçalho C, se for antigo e ainda não tiver esses guardas.

107
Todd Gamblin

No 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 entre todos eles, o compilador C++ criará nomes exclusivos para cada um em um processo chamado decoração ou manipulação de nome. 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 nenhuma manipulação de nome no código entre chaves. Isso permite que você chame funções C de dentro do C++.

21
Trent

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

Para resolver isso, dizemos ao compilador C++ para executar no modo "C", para que ele execute a troca de nomes da mesma maneira que o compilador C faria. Feito isso, os erros do vinculador foram corrigidos.

14
1800 INFORMATION

Quando devemos usá-lo?

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

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

C e C++ usam esquemas diferentes para nomear símbolos. Isso informa ao vinculador para usar o esquema de C ao vincular na biblioteca especificada.

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

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

11
Tony M

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

Em C, a regra é muito simples, todos os símbolos estão em um único espaço de nome. Portanto, o número inteiro "meias" é armazenado como "meias" e a função count_socks é armazenada como "count_socks".

Os vinculadores foram criados para C e outros idiomas como C com esta regra simples de nomeação de símbolos. Portanto, os símbolos no vinculador são apenas sequências simples.

Porém, em C++, a linguagem permite que você tenha espaços para nome, polimorfismo e várias outras coisas que conflitam com uma regra tão simples. Todas as seis funções polimórficas chamadas "add" precisam ter símbolos diferentes ou a incorreta será usada por outros arquivos de objeto. Isso é feito "confundindo" (que é 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 escrito em C, como arquivos de cabeçalho para as bibliotecas C, para informar ao seu compilador C++ que esses nomes de símbolo não devem ser confundidos, enquanto o restante é claro que seu código C++ deve ser confundido ou não funcionará.

10
tialaramex

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, precisará informar ao compilador C++ que o nome dos símbolos que ele está tentando resolver tem uma aparência diferente da padrão; caso contrário, a etapa do link falhará.

7
mbyrne215

Você deve usar "C" externo sempre que incluir um cabeçalho que defina funções que residem em um arquivo compilado por um compilador C, usado em um arquivo C++. (Muitas bibliotecas C padrão podem incluir essa verificação em seus cabeçalhos para simplificar para o desenvolvedor)

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

O que está acontecendo é que o C++ codifica os parâmetros da função em seu nome. É assim que a sobrecarga de funções funciona. 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 ().

O uso externo de "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++.

7
HitScan

A construção extern "C" {} instrui o compilador a não realizar a manipulação de nomes declarados entre chaves. Normalmente, o compilador C++ "aprimora" os nomes das funções para codificar informações de tipo sobre argumentos e o valor de retorno; isso é chamado de nome mutilado. A construção extern "C" impede a distorçã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) a clientes C.

6
Paul Lalonde

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

5
Eric Z Beard

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

Estou movendo esta resposta de: Qual é o efeito de "C" externo em C++? Uma vez que essa pergunta foi considerada uma duplicata desta.

main.cpp

void f() {}
void g();

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

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

Compile com o GCC 4.8 Linux ELF 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 que no código

  • os outros símbolos estavam mutilados. Vamos desmembrá-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 mutilados:

  • 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á de extern "C" tanto ao chamar:

  • C do C++: diga a g++ que espere símbolos não manipulados produzidos por gcc
  • C++ de C: diga a g++ para gerar símbolos não manipulados para gcc usar

Coisas que não funcionam no externo C

Torna-se óbvio que qualquer recurso do C++ que exija a troca de nomes não 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) { }
}

Exemplo de C mínimo executável em C++

Para fins de integridade e novidades, consulte também: Como usar arquivos de origem C em um projeto C++?

Chamar C a partir de C++ é bastante fácil: cada função C possui 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

c.c

#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 extern "C", o link falha com:

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

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

Exemplo no GitHub .

C++ mínimo executável no exemplo C

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

Aqui ilustramos como expor sobrecargas da função 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);
}

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 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 confinados que gcc não podem encontrar.

Exemplo no GitHub .

Testado no Ubuntu 18.04.