Combinazione di C ++ e C: come funziona #ifdef __cplusplus?


319

Sto lavorando a un progetto che ha un sacco di codice C legacy . Abbiamo iniziato a scrivere in C ++, con l'intento di convertire anche il codice legacy. Sono un po 'confuso su come interagiscono C e C ++. Comprendo che racchiudendo il codice C con extern "C"il compilatore C ++ non verranno alterati i nomi del codice C , ma non sono del tutto sicuro di come implementarlo.

Quindi, nella parte superiore di ogni file di intestazione C (dopo le protezioni di inclusione), abbiamo

#ifdef __cplusplus
extern "C" {
#endif

e in fondo, scriviamo

#ifdef __cplusplus
}
#endif

Tra i due, abbiamo tutti i nostri include, typedef e prototipi di funzioni. Ho alcune domande per vedere se lo capisco correttamente:

  1. Se ho un file C ++ A.hh che include un file di intestazione C Bh, include un altro file di intestazione C Ch, come funziona? Penso che quando il compilatore __cpluspluspasserà a Bh, sarà definito, quindi avvolgerà il codice con extern "C" (e __cplusplusnon sarà definito all'interno di questo blocco). Quindi, quando si passa a Ch, __cplusplusnon verrà definito e il codice non verrà inserito extern "C". È corretto?

  2. C'è qualcosa di sbagliato nel racchiudere un pezzo di codice con extern "C" { extern "C" { .. } }? Cosa farà il secondo extern "C" ?

  3. Non inseriamo questo wrapper nei file .c, ma solo nei file .h. Quindi, cosa succede se una funzione non ha un prototipo? Il compilatore pensa che sia una funzione C ++?

  4. Stiamo anche usando un codice di terze parti che è scritto in C e non ha questo tipo di wrapper. Ogni volta che includo un'intestazione da quella libreria, ho inserito un extern "C"#include. È questo il modo giusto di affrontarlo?

  5. Infine, questa impostazione è una buona idea? C'è qualcos'altro che dovremmo fare? Mescoleremo C e C ++ per il prossimo futuro e voglio assicurarmi di coprire tutte le nostre basi.



2
In breve, questa è la migliore spiegazione: To ensure that the names declared in that portion of code have C linkage, and thus C++ name mangling is not performed. (l'ho presa dal link )
anhldbk

Non devi mettere in grassetto il nome del linguaggio C
Edward Karak,

Risposte:


290

extern "C"non cambia davvero il modo in cui il compilatore legge il codice. Se il tuo codice è in un file .c, verrà compilato come C, se è in un file .cpp, verrà compilato come C ++ (a meno che tu non faccia qualcosa di strano nella tua configurazione).

Quello che extern "C"fa è influenzare il collegamento. Le funzioni C ++, una volta compilate, hanno i loro nomi alterati - questo è ciò che rende possibile il sovraccarico. Il nome della funzione viene modificato in base ai tipi e al numero di parametri, in modo che due funzioni con lo stesso nome abbiano nomi di simboli diversi.

Il codice all'interno di un codice extern "C"è ancora C ++. Ci sono delle limitazioni su cosa puoi fare in un blocco "C" esterno, ma sono tutti legati al collegamento. Non è possibile definire nuovi simboli che non possono essere creati con il collegamento C. Ciò significa che non ci sono classi o modelli, per esempio.

extern "C"i blocchi nidificano bene. C'è anche extern "C++"se ti trovi irrimediabilmente intrappolato all'interno delle extern "C"regioni, ma non è una buona idea dal punto di vista della pulizia.

Ora, in particolare riguardo alle tue domande numerate:

Per quanto riguarda # 1: __cplusplus rimarrà definito all'interno dei extern "C"blocchi. Ciò non importa, tuttavia, poiché i blocchi dovrebbero nidificare in modo ordinato.

Per quanto riguarda # 2: __cplusplus sarà definito per qualsiasi unità di compilazione che viene eseguita attraverso il compilatore C ++. In genere, ciò significa che i file .cpp e tutti i file inclusi in tale file .cpp. Lo stesso .h (o .hh o .hpp o what-have-you) potrebbe essere interpretato come C o C ++ in momenti diversi, se diverse unità di compilazione li includono. Se vuoi che i prototipi nel file .h facciano riferimento ai nomi dei simboli C, allora devono avere extern "C"quando vengono interpretati come C ++ e non dovrebbero avere extern "C"quando vengono interpretati come C - quindi il #ifdef __cpluspluscontrollo.

Per rispondere alla tua domanda n. 3: le funzioni senza prototipi avranno un collegamento C ++ se si trovano in file .cpp e non all'interno di un extern "C"blocco. Questo va bene, però, perché se non ha un prototipo, può essere chiamato solo da altre funzioni nello stesso file, e quindi in genere non ti interessa l'aspetto del collegamento, perché non hai intenzione di avere quella funzione essere chiamato da qualunque cosa al di fuori della stessa unità di compilazione.

Per # 4, ce l'hai esattamente. Se si include un'intestazione per il codice che ha un collegamento C (come il codice che è stato compilato da un compilatore C), allora è necessario extern "C"l'intestazione, in questo modo sarà possibile collegarsi con la libreria. (Altrimenti, il tuo linker sarebbe alla ricerca di funzioni con nomi come _Z1hicquando stavi cercandovoid h(int, char)

5: Questo tipo di missaggio è un motivo comune da usare extern "C", e non vedo nulla di male nel farlo in questo modo - assicurati solo di capire cosa stai facendo.


10
Buono a menzionare extern "C++"quando l'intestazione / codice C ++ è intrappolato in profondità all'interno di un codice C
deddebme

1
Ho scritto un semplice programma C. Al suo interno ho aggiunto il blocco #ifdef __cplusplus e aggiunto un printf ("__ cplusplus definito \ n"); dentro. Se lo compilo con gcc, "__cplusplus definito" non viene stampato, ma se lo compilo con g ++, viene stampato. Quindi immagino che __cplusplus significhi che il compilatore è un compilatore C ++ (l'hai detto tu). Non è corretto? (perché ti ho visto dire '__cplusplus dovrebbe essere definito all'interno di blocchi "C" esterni ". Possiamo definire esplicitamente __cplusplus?
Chan Kim

1
Mentre dovresti essere in grado di definire (quasi) tutto ciò che vuoi, il punto __cplusplusè di determinare se C++viene utilizzato vs C, quindi definirlo manualmente / esplicitamente sfugge allo scopo di esso ...
Nurchi

La "C" esterna non riguarda in effetti il ​​modo in cui il compilatore visualizza il file di origine, ma riguarda il modo in cui visualizza il file di intestazione. Le strutture possono avere dimensioni diverse se compilate come C vs C ++, c'è ovviamente il nome che altera e potenzialmente anche altre differenze.
Nick,

39
  1. extern "C"non cambia la presenza o l'assenza della __cplusplusmacro. Cambia solo il collegamento e la modifica del nome delle dichiarazioni incartate.

  2. Puoi nidificare i extern "C"blocchi abbastanza felicemente.

  3. Se compili i tuoi .cfile come C ++, tutto ciò che non è in un extern "C"blocco e senza un extern "C"prototipo verrà trattato come una funzione C ++. Se li compili come C, ovviamente tutto sarà una funzione C.

  4. In questo modo puoi tranquillamente mixare C e C ++.


Se compili .cfile come C ++, tutto viene compilato come codice C ++, anche se si trova in un extern "C"blocco. Il extern "C"codice non può utilizzare funzionalità che dipendono dalle convenzioni di chiamata C ++ (ad es. Sovraccarico dell'operatore) ma il corpo della funzione è ancora compilato come C ++, con tutto ciò che comporta.
David C.,

21

Un paio di gotcha che sono collaterali all'eccellente risposta di Andrew Shelansky e di cui non sono d'accordo non cambiano il modo in cui il compilatore legge il codice

Poiché i prototipi delle funzioni sono compilati come C, non è possibile avere un sovraccarico degli stessi nomi di funzioni con parametri diversi: questa è una delle caratteristiche chiave della modifica del nome del compilatore. È descritto come un problema di collegamento, ma non è del tutto vero: otterrai errori sia dal compilatore che dal linker.

Gli errori del compilatore saranno se si tenta di utilizzare le funzionalità C ++ della dichiarazione del prototipo come il sovraccarico.

Gli errori del linker si verificheranno in seguito perché la tua funzione sembrerà non essere trovata, se non hai il wrapper "C" esterno attorno alle dichiarazioni e l'intestazione è inclusa in un misto di sorgente C e C ++.

Un motivo per scoraggiare le persone dall'uso dell'impostazione di compilazione C come C ++ è perché ciò significa che il loro codice sorgente non è più portabile. Quell'impostazione è un'impostazione di progetto e quindi se un file .c viene rilasciato in un altro progetto, non verrà compilato come c ++. Preferirei che le persone prendessero il tempo di rinominare i suffissi dei file in .cpp.


1
Questa era la causa criptica, che mi strappava i capelli. Ha davvero bisogno di essere pubblicato da qualche parte.
Mitchell Currie,

3

Riguarda l'ABI, al fine di consentire alle applicazioni C e C ++ di utilizzare le interfacce C senza alcun problema.

Poiché il linguaggio C è molto semplice, la generazione del codice è rimasta stabile per molti anni per diversi compilatori, come GCC, Borland C \ C ++, MSVC ecc.

Mentre il C ++ diventa sempre più popolare, molte cose devono essere aggiunte nel nuovo dominio C ++ (ad esempio, finalmente Cfront è stato abbandonato in AT&T perché C non poteva coprire tutte le funzionalità di cui aveva bisogno). Come la funzionalità del modello e la generazione del codice di compilazione-tempo, dal passato, i diversi produttori di compilatori hanno effettivamente implementato l'implementazione del compilatore e del linker C ++ separatamente, gli ABI effettivi non sono affatto compatibili con il programma C ++ su piattaforme diverse.

Le persone potrebbero comunque voler implementare il programma attuale in C ++ ma mantenere comunque la vecchia interfaccia C e ABI come al solito, il file di intestazione deve dichiarare "C" esterno {} , dice al compilatore di generare compatibile / vecchio / semplice / facile C ABI per le funzioni di interfaccia se il compilatore è un compilatore C non un compilatore C ++.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.