Eu tive uma entrevista recentemente e uma pergunta foi: qual é o uso do extern "C"
código C ++. Eu respondi que é para usar funções C no código C ++, pois C não usa nomes diferentes. Perguntaram-me por que C não usa nomes desconfiados e, para ser sincero, não consegui responder.
Entendo que, quando o compilador C ++ compila funções, ele atribui um nome especial à função, principalmente porque podemos ter funções sobrecarregadas com o mesmo nome em C ++, que devem ser resolvidas em tempo de compilação. Em C, o nome da função permanecerá o mesmo, ou talvez com um _ antes dele.
Minha pergunta é: o que há de errado em permitir que o compilador C ++ também modifique as funções C? Eu teria assumido que não importa que nomes o compilador lhes dê. Chamamos funções da mesma maneira em C e C ++.
fonte
extern "C"
diz para alterar o nome da mesma maneira que "o" compilador C faria.Respostas:
Foi meio que respondido acima, mas vou tentar colocar as coisas em contexto.
Primeiro, C veio primeiro. Como tal, o que C faz é, mais ou menos, o "padrão". Não altera nomes porque simplesmente não. Um nome de função é um nome de função. Um global é um global, e assim por diante.
Então C ++ apareceu. O C ++ queria poder usar o mesmo vinculador que C e vincular-se ao código escrito em C. Mas o C ++ não pôde deixar o C "confuso" (ou, falta dele) como está. Confira o seguinte exemplo:
No C ++, essas são funções distintas, com corpos distintos. Se nenhum deles for mutilado, ambos serão chamados de "função" (ou "_função") e o vinculador reclamará da redefinição de um símbolo. A solução C ++ foi alterar os tipos de argumento para o nome da função. Então, um é chamado
_function_int
e o outro é chamado_function_void
(não é o esquema de confinamento real) e a colisão é evitada.Agora ficamos com um problema. Se
int function(int a)
foi definido em um módulo C, e estamos apenas pegando seu cabeçalho (ou seja, declaração) no código C ++ e usando-o, o compilador gerará uma instrução para o vinculador importar_function_int
. Quando a função foi definida, no módulo C, não foi chamada assim. Foi chamado_function
. Isso causará um erro no vinculador.Para evitar esse erro, durante a declaração da função, dizemos ao compilador que é uma função projetada para ser vinculada ou compilada por um compilador C:
O compilador C ++ agora sabe importar
_function
e não_function_int
, e está tudo bem.fonte
-std=c++11
, por exemplo , e evitar o uso de qualquer coisa fora do padrão. É o mesmo que declarar uma versão Java (embora as versões mais recentes do Java sejam compatíveis com versões anteriores). Não é culpa dos padrões que as pessoas usam extensões específicas do compilador e código dependente da plataforma. Por outro lado, você não pode culpá-los, pois há muitas coisas (especialmente IO, como soquetes) ausentes no padrão. O comitê parece estar se aproximando lentamente disso. Corrija-me se eu perdi alguma coisa.Não é que eles "não possam", eles não sejam , em geral.
Se você deseja chamar uma função em uma biblioteca C chamada
foo(int x, const char *y)
, não é bom permitir que o compilador C ++ faça issofoo_I_cCP()
(ou seja o que for, apenas crie um esquema de manipulação aqui) apenas porque pode.Esse nome não será resolvido, a função está em C e seu nome não depende da lista de tipos de argumentos. Portanto, o compilador C ++ precisa saber disso e marcar essa função como C para evitar a manipulação.
Lembre-se de que a função C pode estar em uma biblioteca cujo código-fonte você não possui, tudo o que você tem é o binário pré-compilado e o cabeçalho. Portanto, seu compilador C ++ não pode fazer "é coisa própria", afinal não pode mudar o que está na biblioteca.
fonte
extern "C"
:)Eles não seriam mais funções C.
Uma função não é apenas uma assinatura e uma definição; como uma função funciona é amplamente determinada por fatores como a convenção de chamada. A "Interface Binária do Aplicativo" especificada para uso em sua plataforma descreve como os sistemas se comunicam. A ABI do C ++ em uso pelo seu sistema especifica um esquema de manipulação de nomes, para que os programas nesse sistema saibam invocar funções nas bibliotecas e assim por diante. (Leia a ABI do C ++ Itanium para um ótimo exemplo. Você verá muito rapidamente por que é necessário.)
O mesmo se aplica à ABI C do seu sistema. Algumas ABIs C realmente têm um esquema de identificação de nomes (por exemplo, Visual Studio), portanto, trata-se menos de "desativar a identificação de nomes" e mais sobre como alternar da C ++ ABI para a C ABI, para determinadas funções. Marcamos funções C como sendo funções C, às quais o C ABI (em vez do C ++ ABI) é pertinente. A declaração deve corresponder à definição (seja no mesmo projeto ou em alguma biblioteca de terceiros), caso contrário, a declaração não faz sentido. Sem isso, seu sistema simplesmente não saberá como localizar / chamar essas funções.
Por que as plataformas não definem as ABIs C e C ++ iguais e se livram desse "problema", isso é parcialmente histórico - as AB AB originais não eram suficientes para C ++, que possui namespaces, classes e sobrecarga de operadores, todos dos quais precisam de alguma forma ser representados no nome de um símbolo de uma maneira amigável para o computador - mas também se pode argumentar que fazer com que os programas em C agora respeitem o C ++ seja injusto com a comunidade C, que teria que aturar uma complexidade muito mais complicada ABI apenas pelo interesse de outras pessoas que desejam interoperabilidade.
fonte
+int(PI/3)
, mas com um grão de sal: eu seria muito cauteloso ao falar de "C ++ ABI" ... AFAIK, existem tentativas de definir ABIs C ++, mas nenhum padrão real de fato / de jure - como isocpp.org/files /papers/n4028.pdf afirma (e eu concordo plenamente), citação, é profundamente irônico que o C ++ realmente sempre apoiou uma maneira de publicar uma API com uma ABI binária estável - recorrendo ao subconjunto C do C ++ via extern “C ”. .C++ Itanium ABI
é apenas isso - alguns ++ ABI C para Itanium ... como discutido em stackoverflow.com/questions/7492180/c-abi-issues-listO MSVC de fato altera os nomes C, embora de maneira simples. Às vezes acrescenta
@4
ou outro número pequeno. Isso está relacionado às convenções de chamada e à necessidade de limpeza da pilha.Portanto, a premissa é falha.
fonte
_
?/Gd, /Gr, /Gv, /Gz
. (Ou seja, a convenção de chamada padrão é usada, a menos que uma declaração de função especifique explicitamente uma convenção de chamada). Você está pensando em__cdecl
qual é a convenção de chamada padrão padrão.É muito comum ter programas parcialmente escritos em C e parcialmente escritos em alguma outra linguagem (geralmente linguagem assembly, mas às vezes Pascal, FORTRAN ou outra coisa). Também é comum que os programas contenham componentes diferentes escritos por pessoas diferentes que talvez não tenham o código-fonte para tudo.
Na maioria das plataformas, existe uma especificação - geralmente chamada ABI [Application Binary Interface], que descreve o que um compilador deve fazer para produzir uma função com um nome específico que aceita argumentos de alguns tipos específicos e retorna um valor de algum tipo específico. Em alguns casos, uma ABI pode definir mais de uma "convenção de chamada"; Os compiladores para esses sistemas geralmente fornecem um meio de indicar qual convenção de chamada deve ser usada para uma função específica. Por exemplo, no Macintosh, a maioria das rotinas do Toolbox usa a convenção de chamada Pascal; portanto, o protótipo para algo como "LineTo" seria algo como:
Se todo o código em um projeto foi compilado usando o mesmo compilador, não importaria qual nome o compilador exportou para cada função, mas em muitas situações será necessário que o código C chame funções que foram compiladas usando outras ferramentas e não pode ser recompilado com o compilador atual [e pode muito bem nem estar em C]. Ser capaz de definir o nome do vinculador é, portanto, fundamental para o uso de tais funções.
fonte
Acrescentarei outra resposta, para abordar algumas das discussões tangenciais que ocorreram.
O C ABI (interface binária do aplicativo) originalmente pedia a passagem de argumentos na pilha na ordem inversa (ou seja, pressionada da direita para a esquerda), onde o chamador também libera o armazenamento da pilha. A ABI moderna realmente usa registradores para passar argumentos, mas muitas das considerações desconcertantes remontam à passagem original do argumento da pilha.
O Pascal ABI original, em contraste, empurrou os argumentos da esquerda para a direita, e o chamado teve que aparecer. O ABI C original é superior ao ABI Pascal original em dois pontos importantes. A ordem de envio de argumentos significa que o deslocamento da pilha do primeiro argumento é sempre conhecido, permitindo funções que possuem um número desconhecido de argumentos, onde os argumentos iniciais controlam quantos outros argumentos existem (ala
printf
).A segunda maneira pela qual a C ABI é superior é o comportamento, caso o chamador e o destinatário não concordem com quantos argumentos existem. No caso C, desde que você não acesse argumentos anteriores ao último, nada de ruim acontece. Em Pascal, o número errado de argumentos é exibido na pilha e a pilha inteira está corrompida.
O ABI original do Windows 3.1 foi baseado em Pascal. Como tal, utilizou o Pascal ABI (argumentos da esquerda para a direita, chamados poplee). Como qualquer incompatibilidade no número do argumento pode levar à corrupção da pilha, um esquema confuso foi formado. Cada nome de função foi desconfigurado com um número indicando o tamanho, em bytes, de seus argumentos. Portanto, na máquina de 16 bits, a seguinte função (sintaxe C):
Foi manipulado para
function@2
, porqueint
tem dois bytes de largura. Isso foi feito para que, se a declaração e a definição não coincidirem, o vinculador falhará ao encontrar a função em vez de corromper a pilha no tempo de execução. Por outro lado, se o programa for vinculado, você poderá ter certeza de que o número correto de bytes será exibido da pilha no final da chamada.Windows de 32 bits e em diante use a
stdcall
ABI. É semelhante ao Pascal ABI, exceto que a ordem de envio é como em C, da direita para a esquerda. Assim como a Pascal ABI, o nome mangling manipula o tamanho do byte de argumentos no nome da função para evitar a corrupção da pilha.Diferentemente das declarações feitas em outros lugares aqui, o C ABI não altera os nomes das funções, mesmo no Visual Studio. Por outro lado, as funções de desconfiguração decoradas com a
stdcall
especificação ABI não são exclusivas do VS. O GCC também suporta essa ABI, mesmo ao compilar para Linux. Isso é usado extensivamente pelo Wine , que usa seu próprio carregador para permitir a vinculação em tempo de execução dos binários compilados do Linux às DLLs compiladas do Windows.fonte
Os compiladores C ++ usam nomes diferentes para permitir nomes de símbolos exclusivos para funções sobrecarregadas cuja assinatura seria a mesma. Ele também basicamente codifica os tipos de argumentos, o que permite o polimorfismo em um nível baseado em função.
C não exige isso, pois não permite a sobrecarga de funções.
Observe que o mangling de nome é um (mas certamente não é o único!) Motivo pelo qual não se pode confiar em uma 'C ++ ABI'.
fonte
O C ++ deseja poder interoperar com o código C vinculado a ele ou com o qual está vinculado.
C espera nomes de funções sem nome.
Se o C ++ o manipulasse, ele não encontraria as funções não-mutiladas exportadas de C ou C não encontraria as funções exportadas pelo C ++. O vinculador C deve obter o nome que ele espera, porque não sabe que é proveniente ou está indo para o C ++.
fonte
A manipulação incorreta dos nomes das funções e variáveis C permitiria que seus tipos fossem verificados no momento do link. Atualmente, todas as implementações (?) C permitem definir uma variável em um arquivo e chamá-la como uma função em outro. Ou você pode declarar uma função com uma assinatura incorreta (por exemplo,
void fopen(double)
e depois chamá-la.Propus um esquema para a ligação segura de tipo de variáveis e funções C através do uso de manipulação em 1991. O esquema nunca foi adotado, porque, como outros já observaram aqui, isso destruiria a compatibilidade com versões anteriores.
fonte