Por que precisamos de extern “C” {#include } em C ++?

Por que precisamos usar:

extern "C" { #include  } 

Especificamente:

  • Quando devemos usá-lo?

  • O que está acontecendo no nível do compilador / vinculador que requer que usemos?

  • Como em termos de compilation / vinculação isso resolve os problemas que nos obrigam a usá-lo?

    C e C ++ são superficialmente semelhantes, mas cada um é compilado em um conjunto de códigos muito diferente. Quando você inclui um arquivo de header com um compilador C ++, o compilador está esperando código C ++. Se, no entanto, for um header C, o compilador espera que os dados contidos no arquivo de header sejam compilados em um determinado formato – o C ++ ‘ABI’ ou ‘Application Binary Interface’, de modo que o vinculador se bloqueie. Isso é preferível para passar dados C ++ para uma function que espera dados C.

    (Para entrar na verdade, a ABI do C ++ geralmente ‘manipula’ os nomes de suas funções / methods, então chamando printf() sem sinalizar o protótipo como uma function C, o C ++ realmente gerará código chamando _Zprintf , além de extra porcaria no fim.)

    Então: use extern "C" {...}; quando include o header ac – é simples assim. Caso contrário, você terá uma incompatibilidade no código compilado e o vinculador irá sufocar. Para a maioria dos headers, no entanto, você nem precisará do extern porque a maioria dos headers do sistema C já explicará o fato de que eles podem ser incluídos pelo código C ++ e já extern seu código.

    extern “C” determina como os símbolos no arquivo de object gerado devem ser nomeados. Se uma function for declarada sem o “C” externo, o nome do símbolo no arquivo de object utilizará o mangling do nome C ++. Aqui está um exemplo.

    Dado test.C assim:

     void foo() { } 

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

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

    A function foo é na verdade chamada “_Z3foov”. Esta cadeia contém informações de tipo para o tipo de retorno e parâmetros, entre outras coisas. Se você, em vez disso, escrever test.C assim:

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

    Então compile e olhe para os símbolos:

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

    Você obtém binding C. O nome da function “foo” no arquivo object é apenas “foo”, e não possui todas as informações do tipo extravagante que vêm do mangling de nomes.

    Você geralmente inclui um header dentro do “C” externo {} se o código que o acompanha foi compilado com um compilador C, mas você está tentando chamá-lo de C ++. Quando você faz isso, você está dizendo ao compilador que todas as declarações no header usarão o link C. Quando você vincula seu código, seus arquivos .o conterão referências a “foo”, não “_Z3fooblah”, que corresponderão ao que estiver na biblioteca com a qual você está vinculando.

    A maioria das bibliotecas modernas colocará guardas em torno desses headers para que os símbolos sejam declarados com a binding correta. Por exemplo, em muitos dos headers padrão você encontrará:

     #ifdef __cplusplus extern "C" { #endif ... declarations ... #ifdef __cplusplus } #endif 

    Isso garante que, quando o código C ++ include o header, os símbolos em seu arquivo de object correspondam ao que está na biblioteca C. Você só precisa colocar o “C” {} externo em torno do seu header C se ele for antigo e não tiver esses protetores.

    Em C ++, você pode ter entidades diferentes que compartilham um nome. Por exemplo, aqui está uma lista de funções todas chamadas 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 name-mangling ou decorating. C compiladores 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 mangling de nomes no código dentro das chaves. Isso permite que você chame funções C de dentro do C ++.

    Tem a ver com a maneira como os diferentes compiladores executam o manuseio de nomes. Um compilador C ++ manipulará o nome de um símbolo exportado do arquivo de header de uma maneira completamente diferente de um compilador C, portanto, ao tentar vincular, você obterá um erro de vinculador informando que faltavam símbolos.

    Para resolver isso, dizemos ao compilador C ++ para ser executado no modo “C”, para que ele execute o mangling de nomes da mesma maneira que o compilador C.. Tendo feito isso, os erros do linker são corrigidos.

    Quando devemos usá-lo?

    Quando você está ligando C libaries em arquivos de object C ++

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

    C e C ++ usam esquemas diferentes para nomeação de símbolos. Isso diz ao vinculador para usar o esquema de C ao vincular na biblioteca dada.

    Como em termos de compilation / vinculação isso resolve os problemas que nos obrigam a usá-lo?

    O uso do esquema de nomenclatura C permite referenciar símbolos no estilo C. Caso contrário, o vinculador tentaria símbolos em estilo C ++ que não funcionariam.

    C e C ++ possuem regras diferentes sobre nomes de símbolos. Símbolos são como o vinculador sabe que a chamada para a function “openBankAccount” em um arquivo de object produzido pelo compilador é uma referência àquela function chamada “openBankAccount” em outro arquivo de object produzido a partir de um arquivo de origem diferente (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. Assim, o inteiro “socks” é armazenado como “socks” e a function count_socks é armazenada como “count_socks”.

    Os vinculadores foram criados para C e outros idiomas, como C, com essa regra simples de nomeação de símbolos. Então símbolos no linker são apenas strings simples.

    Mas em C ++, a linguagem permite que você tenha namespaces, polymorphism 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 object. Isso é feito por “mangling” (que é um termo técnico) os nomes dos símbolos.

    Ao vincular código C ++ a bibliotecas C ou código, você precisa de algo externo “C” escrito em C, como arquivos de header para as bibliotecas C, para informar ao seu compilador C ++ que esses nomes de símbolos não serão desconfigurados, enquanto o restante seu código C ++, claro, deve ser mutilado ou não funcionará.

    Você deve usar extern “C” sempre que include um header definindo funções que residem em um arquivo compilado por um compilador C, usado em um arquivo C ++. (Muitas bibliotecas C padrão podem include essa verificação em seus headers para torná-lo mais simples para o desenvolvedor)

    Por exemplo, se você tem um projeto com 3 arquivos, util.c, util.h e main.cpp e ambos os arquivos .c e .cpp são compilados com o compilador C ++ (g ++, cc, etc), então não é É realmente necessário, e pode até causar erros de linker. Se o seu processo de compilation usa um compilador C regular para o util.c, então você precisará usar o “C” externo ao include util.h.

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

    Usando extern “C” resolve o problema acima, dizendo ao compilador C ++ que ele deve procurar por uma function que segue a convenção de nomenclatura C em vez da C ++.

    A construção extern "C" {} instrui o compilador a não executar mangling em nomes declarados dentro das chaves. Normalmente, o compilador C ++ “aprimora” nomes de function para que eles codifiquem informações de tipo sobre argumentos e o valor de retorno; isso é chamado de nome mutilado . A construção extern "C" impede a mutilação.

    É normalmente usado quando o código C ++ precisa chamar uma biblioteca de linguagem C. Ele também pode ser usado ao expor uma function C ++ (de uma DLL, por exemplo) para clientes 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 function que resida em um arquivo C, compilada como código C, será necessário informar ao compilador C ++ que os nomes dos símbolos que ele está tentando resolver têm aparência diferente do padrão; caso contrário, a etapa do link falhará.

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