Combinando C ++ e C - como funciona o #ifdef __cplusplus?

319

Estou trabalhando em um projeto que possui muito código C legado . Começamos a escrever em C ++, com a intenção de, eventualmente, converter também o código legado. Estou um pouco confuso sobre como o C e C ++ interagem. Entendo que envolver o código C com extern "C"o compilador C ++ não alterará os nomes do código C , mas não tenho muita certeza de como implementá-lo.

Portanto, na parte superior de cada arquivo de cabeçalho C (após os protetores de inclusão), temos

#ifdef __cplusplus
extern "C" {
#endif

e na parte inferior, escrevemos

#ifdef __cplusplus
}
#endif

Entre os dois, temos todos os nossos incluem, typedefs e protótipos de função. Tenho algumas perguntas para verificar se estou entendendo isso corretamente:

  1. Se eu tiver um arquivo C ++ A.hh que inclua um arquivo de cabeçalho C Bh, inclua outro arquivo de cabeçalho C Ch, como isso funciona? Eu acho que quando o compilador entrar em Bh, __cplusplusserá definido, então ele envolverá o código com extern "C" (e __cplusplusnão será definido dentro deste bloco). Portanto, quando entrar em Ch, __cplusplusnão será definido e o código não será envolvido extern "C". Isso está correto?

  2. Há algo de errado em envolver um pedaço de código extern "C" { extern "C" { .. } }? O que o segundo extern "C" fará?

  3. Não colocamos esse wrapper nos arquivos .c, apenas nos arquivos .h. Então, o que acontece se uma função não tiver um protótipo? O compilador acha que é uma função C ++?

  4. Também estamos usando algum código de terceiros que está escrito em C e não possui esse tipo de wrapper. Sempre que incluo um cabeçalho dessa biblioteca, venho colocando uma questão em extern "C"torno da #include. É este o caminho certo para lidar com isso?

  5. Finalmente, isso é uma boa ideia? Há mais alguma coisa que devemos fazer? Vamos misturar C e C ++ no futuro próximo, e quero ter certeza de que estamos cobrindo todas as nossas bases.

Dublin
fonte
2
Concisa, esta é a melhor explicação: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (Eu tenho que partir do link )
anhldbk
Você não precisa colocar o nome do idioma C em negrito
Edward Karak

Respostas:

290

extern "C"realmente não muda a maneira como o compilador lê o código. Se o seu código estiver em um arquivo .c, ele será compilado como C, se estiver em um arquivo .cpp, será compilado como C ++ (a menos que você faça algo estranho à sua configuração).

O que extern "C"faz é afetar a ligação. As funções C ++, quando compiladas, têm seus nomes mutilados - é isso que torna a sobrecarga possível. O nome da função é modificado com base nos tipos e no número de parâmetros, para que duas funções com o mesmo nome tenham nomes de símbolos diferentes.

O código dentro de um extern "C"ainda é código C ++. Existem limitações sobre o que você pode fazer em um bloco externo "C", mas tudo sobre ligação. Você não pode definir nenhum novo símbolo que não possa ser construído com o vínculo C. Isso significa que não há classes ou modelos, por exemplo.

extern "C"blocos ninho bem. Também existe extern "C++"se você se encontrar irremediavelmente preso dentro de extern "C"regiões, mas não é uma boa idéia do ponto de vista da limpeza.

Agora, especificamente sobre suas perguntas numeradas:

Em relação ao item 1: __cplusplus permanecerá definido dentro dos extern "C"blocos. Porém, isso não importa, já que os blocos devem se encaixar perfeitamente.

Em relação a # 2: __cplusplus será definido para qualquer unidade de compilação que esteja sendo executada no compilador C ++. Geralmente, isso significa arquivos .cpp e todos os arquivos incluídos nesse arquivo .cpp. O mesmo .h (ou .hh ou .hpp ou o que você tem) pode ser interpretado como C ou C ++ em momentos diferentes, se diferentes unidades de compilação os incluírem. Se você deseja que os protótipos no arquivo .h se refiram aos nomes dos símbolos C, eles devem ter extern "C"ao serem interpretados como C ++, e não devem ter extern "C"ao serem interpretados como C - daí a #ifdef __cplusplusverificação.

Para responder à sua pergunta nº 3: funções sem protótipos terão ligação C ++ se estiverem em arquivos .cpp e não dentro de um extern "C"bloco. Isso é bom, no entanto, porque, se não tiver um protótipo, só poderá ser chamado por outras funções no mesmo arquivo e, em geral, você não se importará com a aparência da ligação, porque não planeja ter essa função. seja chamado por qualquer coisa fora da mesma unidade de compilação.

Para o nº 4, você conseguiu exatamente. Se você estiver incluindo um cabeçalho para o código que possui ligação C (como o código que foi compilado por um compilador C), será necessário extern "C"o cabeçalho - para que você possa vincular à biblioteca. (Caso contrário, seu vinculador procuraria funções com nomes como _Z1hicquando você estava procurandovoid h(int, char)

5: Esse tipo de mixagem é um motivo comum de se usar extern "C", e não vejo nada de errado em fazer dessa maneira - apenas certifique-se de entender o que está fazendo.

Andrew Shelansky
fonte
10
Bom para mencionar extern "C++"quando seu cabeçalho C ++ / code está preso profundamente dentro de algum código C
deddebme
1
Eu escrevi um programa C simples. Dentro dele, adicionei o bloco #ifdef __cplusplus e adicionei um printf ("__ cplusplus definido \ n"); iniciar. Se eu compilar com gcc, "__cplusplus definido" não será impresso, mas se eu compilar com g ++, será impresso. Então, eu acho que __cplusplus significa que o compilador é um compilador C ++ (você o disse). Não está correto? (porque eu vi você dizendo '__cplusplus deve estar dentro definido de blocos externo 'C'' podemos definir __cplusplus explicitamente.?
Chan Kim
1
Enquanto você deve ser capaz de definir (quase) qualquer coisa que você quiser, todo o ponto de __cplusplusé determinar se C++está sendo usado vs C, então defini-lo manualmente / desafia explicitamente o objetivo dele ...
nurchi
O "C" externo não é realmente sobre como o compilador visualiza o arquivo de origem, é sobre como ele visualiza o arquivo de cabeçalho. estruturas podem ter tamanhos diferentes quando compiladas como C vs C ++, existe o nome desconcertante, é claro, e potencialmente outras diferenças também.
Nick
39
  1. extern "C"não altera a presença ou ausência da __cplusplusmacro. Ele apenas altera a ligação e a identificação de nomes das declarações agrupadas.

  2. Você pode aninhar extern "C"blocos muito feliz.

  3. Se você compilar seus .carquivos como C ++, qualquer coisa que não esteja em um extern "C"bloco e sem um extern "C"protótipo será tratada como uma função C ++. Se você os compilar como C, é claro que tudo será uma função C.

  4. sim

  5. Você pode misturar com segurança C e C ++ dessa maneira.

Anthony Williams
fonte
Se você compilar .carquivos como C ++, tudo será compilado como código C ++, mesmo que esteja em um extern "C"bloco. O extern "C"código não pode usar recursos que dependem de convenções de chamada C ++ (por exemplo, sobrecarga do operador), mas o corpo da função ainda é compilado como C ++, com tudo o que isso implica.
David C.
21

Algumas dicas que são colunistas da excelente resposta de Andrew Shelansky e que discordam um pouco não mudam realmente a maneira como o compilador lê o código

Como os protótipos de suas funções são compilados como C, não é possível sobrecarregar os mesmos nomes de funções com parâmetros diferentes - esse é um dos principais recursos do nome desconfigurado do compilador. É descrito como um problema de ligação, mas isso não é verdade - você receberá erros do compilador e do vinculador.

Os erros do compilador ocorrerão se você tentar usar os recursos C ++ da declaração de protótipo, como sobrecarga.

Os erros do vinculador ocorrerão mais tarde porque sua função parecerá não ser encontrada, se você não tiver o wrapper externo "C" em torno das declarações e o cabeçalho estiver incluído em uma mistura de fontes C e C ++.

Um motivo para desencorajar as pessoas a usar a configuração de compilação C como C ++ é porque isso significa que o código-fonte não é mais portátil. Essa configuração é uma configuração de projeto e, se um arquivo .c for solto em outro projeto, ele não será compilado como c ++. Prefiro que as pessoas renomeiem os sufixos de arquivo para .cpp.

Andy Dent
fonte
1
Esta foi a causa enigmática, arrancando meu cabelo. Realmente precisa ser publicado em algum lugar.
Mitchell Currie
3

É sobre a ABI, para permitir que aplicativos C e C ++ usem interfaces C sem nenhum problema.

Como a linguagem C é muito fácil, a geração de código ficou estável por muitos anos para diferentes compiladores, como GCC, Borland C \ C ++, MSVC etc.

Enquanto o C ++ se torna cada vez mais popular, muitas coisas devem ser adicionadas ao novo domínio C ++ (por exemplo, finalmente, o Cfront foi abandonado na AT&T porque o C não podia cobrir todos os recursos necessários). Como o recurso de modelo e a geração de código em tempo de compilação, no passado, os diferentes fornecedores do compilador realmente fizeram a implementação real do compilador e vinculador C ++ separadamente, as ABIs reais não são de todo compatíveis com o programa C ++ em plataformas diferentes.

As pessoas ainda podem gostar de implementar o programa real em C ++, mas ainda mantêm a interface C e a ABI antigas, como de costume, o arquivo de cabeçalho deve declarar extern "C" {} , informa ao compilador gerar C ABI compatível / antiga / simples / fácil para as funções de interface se o compilador for compilador C e não compilador C ++.

Bo Zhou
fonte