O que exatamente a inserção extern "C"
no código C ++ faz?
Por exemplo:
extern "C" {
void foo();
}
foo()
função.
O que exatamente a inserção extern "C"
no código C ++ faz?
Por exemplo:
extern "C" {
void foo();
}
foo()
função.
Respostas:
extern "C" faz com que um nome de função em C ++ tenha ligação 'C' (o compilador não altera o nome) para que o código C do cliente possa vincular (ou seja, usar) sua função usando um arquivo de cabeçalho compatível com 'C' que contenha apenas o declaração de sua função. Sua definição de função está contida em um formato binário (que foi compilado pelo seu compilador C ++) que o vinculador 'C' do cliente vinculará ao uso do nome 'C'.
Como o C ++ possui sobrecarga de nomes de funções e C, o compilador C ++ não pode apenas usar o nome da função como um ID exclusivo para vincular, portanto, gerencia o nome adicionando informações sobre os argumentos. O compilador AC não precisa alterar o nome, pois você não pode sobrecarregar os nomes das funções em C. Quando você declara que uma função possui ligação "C" externa em C ++, o compilador C ++ não adiciona informações de tipo de argumento / parâmetro ao nome usado para ligação.
Só para você saber, é possível especificar explicitamente a ligação "C" para cada declaração / definição individual explicitamente ou usar um bloco para agrupar uma sequência de declarações / definições para ter uma determinada ligação:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Se você se preocupa com os detalhes técnicos, eles estão listados na seção 7.5 do padrão C ++ 03, aqui está um breve resumo (com ênfase no externo "C"):
extern "C" { int i; }
é uma definição. Pode não ser o que você pretendia, ao lado da não definição de void g(char);
. Para torná-lo uma não definição, você precisaria extern "C" { extern int i; }
. Por outro lado, a sintaxe de uma declaração sem chaves faz a declaração não-definição: extern "C" int i;
é o mesmo queextern "C" { extern int i; }
Só queria adicionar um pouco de informação, pois ainda não a vi publicada.
Você verá frequentemente o código nos cabeçalhos C da seguinte forma:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
O que isso faz é que ele permite que você use esse arquivo de cabeçalho C com seu código C ++, porque a macro "__cplusplus" será definida. Mas você também pode usá-lo com o código C legado, onde a macro NÃO está definida, para que não veja a construção exclusiva do C ++.
Embora eu também tenha visto código C ++, como:
extern "C" {
#include "legacy_C_header.h"
}
que imagino realiza praticamente a mesma coisa.
Não tenho certeza qual caminho é melhor, mas eu já vi os dois.
extern "C"
no cabeçalho). Funciona muito bem, usei essa técnica várias vezes.
extern "C"
antes ou depois de incluir o cabeçalho. Quando chega ao compilador, é apenas um longo fluxo de texto pré-processado.
g++
errou, para qualquer objetivo, a qualquer momento nos últimos 17 anos, pelo menos. O ponto principal do primeiro exemplo é que não importa se você usa um compilador C ou C ++, nenhuma manipulação de nome será feita para os nomes no extern "C"
bloco.
Descompile um g++
binário gerado para ver o que está acontecendo
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 e desmonte a saída ELF gerada :
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
A saída contém:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 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: 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 para gcc
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
:
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 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
#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++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
cc
#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 mutilado f
, que gcc
não produziu.
Exemplo de C ++ executável mínimo do exemplo C
Chamar C ++ de 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 mutilados que gcc
não conseguem encontrar.
Testado no Ubuntu 18.04.
extern "C" {
ajuda a chamar funções C não manipuladas dentro de programas C ++ , bem como funções C ++ não manipuladas dentro de programas C , que outras respostas não tornam tão óbvias e 2) porque você mostra exemplos distintos de cada. Obrigado!
Em todo programa C ++, todas as funções não estáticas são representadas no arquivo binário como símbolos. Esses símbolos são seqüências de texto especiais que identificam exclusivamente uma função no programa.
Em C, o nome do símbolo é igual ao nome da função. Isso é possível porque, em C, duas funções não estáticas podem ter o mesmo nome.
Como o C ++ permite sobrecarga e possui muitos recursos que C - como classes, funções-membro, especificações de exceção - não é possível simplesmente usar o nome da função como o nome do símbolo. Para resolver isso, o C ++ usa o chamado mangling de nome, que transforma o nome da função e todas as informações necessárias (como o número e o tamanho dos argumentos) em uma string de aparência estranha processada apenas pelo compilador e vinculador.
Portanto, se você especificar uma função para ser externa C, o compilador não executará a troca de nome com ela e poderá ser acessado diretamente usando o nome do símbolo como o nome da função.
Isso é útil ao usar dlsym()
e dlopen()
para chamar essas funções.
A maioria das linguagens de programação não é construída sobre as linguagens de programação existentes. O C ++ é construído sobre o C e, além disso, é uma linguagem de programação orientada a objetos, construída a partir de uma linguagem de programação procedural e, por esse motivo, existem expressões C ++ comoextern "C"
que fornecem compatibilidade retroativa com C.
Vejamos o seguinte exemplo:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
void printMe(int a) {
printf("int: %i\n", a);
}
void printMe(char a) {
printf("char: %c\n", a);
}
int main() {
printMe("a");
printMe(1);
return 0;
}
O compilador CA não compilará o exemplo acima, porque a mesma função printMe
é definida duas vezes (mesmo que eles tenham parâmetros diferentes int a
vs char a
).
gcc -o printMe printMe.c && ./printMe;
1 erro. PrintMe é definido mais de uma vez.
Um compilador C ++ compilará o exemplo acima. Não importa se printMe
é definido duas vezes.
g ++ -o printMe printMe.c && ./printMe;
Isso ocorre porque um compilador C ++ renomeia implicitamente ( manipula ) funções com base em seus parâmetros. Em C, esse recurso não era suportado. No entanto, quando o C ++ foi construído sobre C, a linguagem foi projetada para ser orientada a objetos e necessária para oferecer suporte à capacidade de criar classes diferentes com métodos (funções) com o mesmo nome e substituir métodos ( substituição de método ) com base em diferentes parâmetros.
extern "C"
diz "não altere nomes de funções C"No entanto, imagine que tenhamos um arquivo C herdado chamado "parent.c", que include
é o nome da função de outros arquivos C herdados, "parent.h", "child.h" etc. Se o arquivo herdado "parent.c" for executado por meio de um compilador C ++, os nomes das funções serão desconfigurados e não corresponderão mais aos nomes de funções especificados em "parent.h", "child.h" etc. - para que os nomes de funções nesses arquivos externos também precisem ser mutilado. A manipulação de nomes de funções em um programa C complexo, com muitas dependências, pode levar ao código quebrado; portanto, pode ser conveniente fornecer uma palavra-chave que diga ao compilador C ++ para não alterar o nome de uma função.
A extern "C"
palavra-chave informa ao compilador C ++ para não alterar (renomear) nomes de função C.
Por exemplo:
extern "C" void printMe(int a);
extern "C"
se tivermos apenas um dll
arquivo? Quero dizer, se não temos um arquivo de cabeçalho e apenas temos um arquivo de origem (apenas implementações) e o uso de sua função via ponteiro de função. nesse estado, usamos apenas funções (independentemente de seu nome).
Nenhum cabeçalho C pode ser compatível com C ++ apenas envolto em "C" externo. Quando os identificadores em um cabeçalho C entram em conflito com as palavras-chave C ++, o compilador C ++ se queixa disso.
Por exemplo, vi o seguinte código falhar em um g ++:
extern "C" {
struct method {
int virtual;
};
}
Meio que faz sentido, mas é algo a ter em mente ao portar código C para C ++.
extern "C"
significa usar ligação C, conforme descrito por outras respostas. Não significa "compilar o conteúdo como C" ou qualquer coisa. int virtual;
é inválido em C ++ e a especificação de ligação diferente não altera isso.
Ele altera o vínculo de uma função de forma que a função possa ser chamada de C. Na prática, isso significa que o nome da função não é mutilado .
undname
.
Ele informa o compilador C ++ a procurar os nomes dessas funções no estilo C ao vincular, porque os nomes das funções compiladas em C e C ++ são diferentes durante o estágio de vinculação.
Eu usei 'extern "C"' antes para arquivos dll (biblioteca de vínculo dinâmico) tornarem a função main () "exportável" para que possa ser usado posteriormente em outro executável da dll. Talvez um exemplo de onde eu costumava usá-lo possa ser útil.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
Exe
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
e __declspec(dllexport)
não são relacionados. O primeiro controla a decoração do símbolo, o segundo é responsável por criar uma entrada de exportação. Você também pode exportar um símbolo usando a decoração de nome C ++. Além de perder completamente o objetivo desta questão, também existem outros erros no exemplo de código. Por um lado, main
exportado da sua DLL não declara um valor de retorno. Ou convocando convenção, para esse assunto. Ao importar, você atribui uma convenção de chamada aleatória ( WINAPI
) e usa o símbolo errado para compilações de 32 bits (deve ser _main
ou _main@0
). Desculpe -1.
void*
, mas sua implementação não retorna nada. Isso vai voar muito bem ... #
extern "C"
é uma especificação de ligação usada para chamar funções C nos arquivos de origem Cpp . Podemos chamar funções C, escrever variáveis e incluir cabeçalhos . A função é declarada na entidade externa e é definida fora. A sintaxe é
Tipo 1:
extern "language" function-prototype
Tipo 2:
extern "language"
{
function-prototype
};
por exemplo:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Esta resposta é para os impacientes / com prazos a cumprir, apenas uma explicação parcial / simples está abaixo:
Portanto,
em C ++, com o nome desconectado identifica cada função
em C, mesmo sem o nome desconectado identifica cada função
Para alterar o comportamento do C ++, ou seja, para especificar que o nome incorreto não ocorra para uma função específica, você pode usar "C" externo antes do nome da função, por qualquer motivo, como exportar uma função com um nome específico de uma dll , para uso de seus clientes.
Leia outras respostas, para obter respostas mais detalhadas / corretas.
Ao misturar C e C ++ (ou seja, a. Chamando a função C a partir de C ++; e b. Chamando a função C ++ a partir de C), o nome C ++ incorreto causa problemas de vinculação. Tecnicamente falando, esse problema ocorre apenas quando as funções de chamada já foram compiladas no binário (provavelmente, um arquivo de biblioteca * .a) usando o compilador correspondente.
Portanto, precisamos usar extern "C" para desativar o nome desconectado em C ++.
Sem entrar em conflito com outras boas respostas, adicionarei um pouco do meu exemplo.
O que exatamente o C ++ Compiler faz: ele gerencia os nomes no processo de compilação; portanto, exigimos que o compilador trate a C
implementação especialmente.
Quando estamos criando e adicionando classes C ++ extern "C"
, estamos dizendo ao nosso compilador C ++ que estamos usando a convenção de chamada em C.
Razão (estamos chamando a implementação C em C ++): queremos chamar a função C em C ++ ou a função C ++ em C (classes C ++ ... etc, não funcionam em C).
Uma função void f () compilada por um compilador C e uma função com o mesmo nome void f () compilada por um compilador C ++ não são a mesma função. Se você escreveu essa função em C e tentou chamá-la de C ++, o vinculador procuraria a função C ++ e não encontraria a função C.
extern "C" informa ao compilador C ++ que você possui uma função que foi compilada pelo compilador C. Depois que você disser que foi compilado pelo compilador C, o compilador C ++ saberá como chamá-lo corretamente.
Ele também permite que o compilador C ++ compile uma função C ++ de forma que o compilador C possa chamá-la. Essa função seria oficialmente uma função C, mas como é compilada pelo compilador C ++, ela pode usar todos os recursos do C ++ e possui todas as palavras-chave do C ++.
extern "C"
função - e (sujeito a algumas restrições) será possível chamar pelo código compilado por um compilador C.