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

Eu estou trabalhando em um projeto que tem um monte de código C legado. Nós começamos a escrever em C ++, com a intenção de eventualmente converter o código legado, também. Estou um pouco confuso sobre como o C e o C ++ interagem. Eu entendo que ao envolver o código C com o extern "C" o compilador C ++ não manipulará os nomes do código C , mas não tenho certeza de como implementar isso.

Então, no topo de cada arquivo de header C (depois dos guardas de inclusão), temos

 #ifdef __cplusplus extern "C" { #endif 

e na parte inferior, escrevemos

 #ifdef __cplusplus } #endif 

Entre os dois, temos todos os nossos protótipos includes, typedefs e function. Eu tenho algumas perguntas, para ver se estou entendendo isso corretamente:

  1. Se eu tenho um arquivo C ++ A.hh que inclui um arquivo de header C Bh, inclui outro arquivo de header C Ch, como isso funciona? Eu acho que quando o compilador entra em Bh, o __cplusplus será definido, então ele irá envolver o código com o extern "C" (e o __cplusplus não será definido dentro deste bloco). Então, quando ele entrar em Ch, o __cplusplus não será definido e o código não será empacotado no extern "C" . Isso está correto?

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

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

  4. Também estamos usando algum código de terceiros que está escrito em C e não tem esse tipo de wrapper em torno dele. Sempre que incluo um header dessa biblioteca, extern "C" um extern "C" ao redor do #include. Este é o caminho certo para lidar com isso?

  5. Finalmente, isso é uma boa ideia? Há mais alguma coisa que devemos fazer? Nós estaremos misturando C e C ++ no futuro previsível, e eu quero ter certeza de que estamos cobrindo todas as nossas bases.

    extern "C" realmente não altera 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, ele será compilado como C ++ (a menos que você faça algo estranho em sua configuração).

    O que extern "C" faz é afetar a binding. As funções do C ++, quando compiladas, têm seus nomes mutilados – isto é o que torna a sobrecarga possível. O nome da function é modificado com base nos tipos e números de parâmetros, de modo que duas funções com o mesmo nome tenham nomes de símbolos diferentes.

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

    Blocos extern "C" encheckboxm bem. Há também o extern "C++" se você se encontrar preso dentro de regiões extern "C" , mas não é uma boa idéia do ponto de vista da limpeza.

    Agora, especificamente em relação às suas perguntas numeradas:

    Em relação ao # 1: __cplusplus deve ser definido dentro de blocos extern "C" . Isso não importa, entretanto, já que os blocos devem se aninhar perfeitamente.

    Em relação a # 2: o __cplusplus será definido para qualquer unidade de compilation que esteja sendo executada através do compilador C ++. Geralmente, isso significa arquivos .cpp e quaisquer arquivos incluídos por esse arquivo .cpp. O mesmo .h (ou .hh ou .hpp ou what-have-you) poderia ser interpretado como C ou C ++ em momentos diferentes, se diferentes unidades de compilation incluí-los. Se você quiser 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" quando interpretados como C – daí o #ifdef __cplusplus verificação #ifdef __cplusplus .

    Para responder sua pergunta # 3: funções sem protótipos terão binding C ++ se estiverem em arquivos .cpp e não dentro de um bloco extern "C" . Isso é bom, porque, se não tem protótipo, só pode ser chamado por outras funções no mesmo arquivo, e então você geralmente não se importa com a aparência da binding, porque você não está planejando ter essa function. ser chamado por qualquer coisa fora da mesma unidade de compilation de qualquer maneira.

    Para o número 4, você entendeu exatamente. Se você está incluindo um header para o código que possui uma binding C (como um código que foi compilado por um compilador C), então você deve extern "C" o header extern "C" – dessa forma você será capaz de se conectar à biblioteca. (Caso contrário, seu vinculador estaria procurando funções com nomes como _Z1hic quando você estava procurando por void h(int, char)

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

    1. extern "C" não altera a presença ou ausência da macro __cplusplus . Ele apenas altera a binding e o nome de manuseio das declarações envolvidas.

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

    3. Se você compilar seus arquivos .c como C ++, qualquer coisa que não esteja em um bloco extern "C" e sem um protótipo extern "C" será tratada como uma function C ++. Se você compilá-los como C, então tudo será uma function C.

    4. sim

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

    Um par de pegadinhas que são colloraries para excelente resposta de Andrew Shelansky e discordar um pouco com realmente não muda a maneira que o compilador lê o código

    Como os protótipos de funções são compilados como C, não é possível sobrecarregar os mesmos nomes de function com parâmetros diferentes – essa é uma das principais características do nome mangling do compilador. Ele é descrito como um problema de binding, mas isso não é bem verdade – você obterá erros do compilador e do vinculador.

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

    Os erros de linker ocorrerão mais tarde porque sua function parecerá não ser encontrada, se você não tiver o wrapper “C” externo ao redor das declarações e o header estiver incluído em uma mistura de origem C e C ++.

    Um motivo para desencorajar as pessoas de usar a configuraçã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, portanto, se um arquivo .c for solto em outro projeto, ele não será compilado como c ++. Eu prefiro que as pessoas tomem o tempo para renomear os sufixos de arquivo para .cpp.