C e C ++ são superficialmente semelhantes, 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 chamada de código _Zprintf, além de porcaria extra no final. )
Portanto: use extern "C" {...}ao incluir o cabeçalho CA - é 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á disso externporque a maioria dos cabeçalhos C do sistema já consideram o fato de que eles podem ser incluídos no código C ++ e já no externcódigo.
Você poderia, por favor, elaborar mais sobre "a maioria dos cabeçalhos C do sistema já considerará o fato de que eles podem ser incluídos no código C ++ e já exteriores ao código". ?
Bulat M.
7
@BulatM. Eles contêm algo como isto: #ifdef __cplusplus extern "C" { #endif Portanto, quando incluídos em um arquivo C ++, eles ainda são tratados como um cabeçalho C.
Calmarius 13/03
111
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 é chamada "_Z3foov". Essa sequência contém informações de tipo para o tipo e parâmetros de retorno, 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 que derivam da seleção de nomes.
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á:
Isso garante que, quando o código C ++ inclua o cabeçalho, os símbolos no arquivo de objeto correspondam ao que está na biblioteca C. Você só deve colocar "C" externo {} em volta do cabeçalho C, se for antigo e ainda não tiver esses guardas.
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, 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 ++.
Isso 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.
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 "adicionar" 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 do é claro que seu código C ++ deve ser confundido ou não funcionará.
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.
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 o seu processo de construção usa um compilador C regular para o util.c, você precisará usar o "C" externo ao incluir o utilitário.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 ++.
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á.
A extern "C" {}construção instrui o compilador a não executar 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 extern "C"construção evita 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.
Num:ValueSizeTypeBindVisNdxName8:00000000000000006 FUNC GLOBAL DEFAULT 1 _Z1fv
9:00000000000000066 FUNC GLOBAL DEFAULT 1 ef
10:000000000000000c16 FUNC GLOBAL DEFAULT 1 _Z1hv
11:00000000000000000 NOTYPE GLOBAL DEFAULT UND _Z1gv
12:00000000000000000 NOTYPE GLOBAL DEFAULT UND eg
Interpretação
Nós vemos que:
efe egforam armazenados em símbolos com o mesmo nome que no código
os outros símbolos estavam mutilados. Vamos desmembrá-los:
Conclusão: os dois tipos de símbolos a seguir não foram 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"ambos ao ligar:
C de C ++: diga g++para esperar símbolos não manipulados produzidos porgcc
C ++ de C: diga g++para gerar símbolos não manipulados para gccusar
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 extern C:
extern"C"{// Overloading.// error: declaration of C function ‘void f(int)’ conflicts withvoid f();void f(int i);// Templates.// error: template with C linkagetemplate<class C>void f(C i){}}
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.
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */#ifdef __cplusplus
extern"C"{#endifint f();#ifdef __cplusplus
}#endif#endif
#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"{#endifint 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);}
#ifdef __cplusplus extern "C" { #endif
Portanto, quando incluídos em um arquivo C ++, eles ainda são tratados como um cabeçalho C.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:
Compilar e listar símbolos no arquivo de objeto fornece:
A função foo é chamada "_Z3foov". Essa sequência contém informações de tipo para o tipo e parâmetros de retorno, entre outras coisas. Se você escrever test.C, assim:
Em seguida, compile e observe os símbolos:
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 que derivam da seleção de nomes.
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á:
Isso garante que, quando o código C ++ inclua o cabeçalho, os símbolos no arquivo de objeto correspondam ao que está na biblioteca C. Você só deve colocar "C" externo {} em volta do cabeçalho C, se for antigo e ainda não tiver esses guardas.
fonte
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, 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 ++.
fonte
Isso 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.
fonte
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 "adicionar" 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 do é claro que seu código C ++ deve ser confundido ou não funcionará.
fonte
Quando você está vinculando bibliotecas C em arquivos de objeto C ++
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.
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.
fonte
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 o seu processo de construção usa um compilador C regular para o util.c, você precisará usar o "C" externo ao incluir o utilitário.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 ++.
fonte
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á.
fonte
A
extern "C" {}
construção instrui o compilador a não executar 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 . Aextern "C"
construção evita 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.
fonte
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".
fonte
Descompile um
g++
binário gerado para ver o que está acontecendoPara entender por que
extern
é necessário, a melhor coisa a fazer é entender o que está acontecendo em detalhes nos arquivos de objeto com um exemplo:main.cpp
Compile com a saída ELF do GCC 4.8 Linux :
Descompile a tabela de símbolos:
A saída contém:
Interpretação
Nós vemos que:
ef
eeg
foram armazenados em símbolos com o mesmo nome que no códigoos outros símbolos estavam mutilados. Vamos desmembrá-los:
Conclusão: os dois tipos de símbolos a seguir não foram mutilados:
Ndx = UND
), a ser fornecido no link ou no tempo de execução de outro arquivo de objetoEntão, você precisará de
extern "C"
ambos ao ligar:g++
para esperar símbolos não manipulados produzidos porgcc
g++
para gerar símbolos não manipulados paragcc
usarCoisas 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
extern C
:Exemplo de C executável mínimo de 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
CH
cc
Corre:
Sem
extern "C"
o link falha com:porque
g++
espera encontrar um mutiladof
, quegcc
não produziu.Exemplo no GitHub .
Exemplo C ++ executável mínimo do 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
cpp.h
cpp.cpp
Corre:
Sem
extern "C"
ele falha com:porque
g++
gerou símbolos mutilados quegcc
não podem ser encontrados.Exemplo no GitHub .
Testado no Ubuntu 18.04.
fonte