Combinare C ++ e C – come funziona #ifdef __cplusplus?

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 ++. Capisco che avvolgendo il codice C con extern "C" il compilatore C ++ non manipolerà 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 inclusi, typedef e prototipi di funzioni. Ho alcune domande, per vedere se sto capendo 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 passi in Bh, verrà definito __cplusplus , quindi avvolgerà il codice con extern "C" (e __cplusplus non verrà definito all’interno di questo blocco). Quindi, quando entra in Ch, __cplusplus non verrà definito e il codice non verrà __cplusplus nella extern "C" . È corretto?

  2. C’è qualcosa di sbagliato nel confezionare un pezzo di codice con extern "C" { extern "C" { .. } } ? Cosa farà la seconda extern "C" ?

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

  4. Stiamo anche utilizzando un codice di terze parti scritto in C , che non ha questo tipo di wrapper. Ogni volta che includo un’intestazione da quella libreria, ho inserito una extern "C" attorno al #include. È questo il modo giusto per affrontarlo?

  5. Infine, questa è una buona idea? C’è qualcos’altro che dovremmo fare? Stiamo andando a mescolare C e C ++ per il prossimo futuro, e voglio essere sicuro che stiamo coprendo tutte le nostre basi.

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

La extern "C" influisce sul collegamento. Le funzioni del C ++, quando compilate, hanno i loro nomi storpiati – questo è ciò che rende ansible il sovraccarico. Il nome della funzione viene modificato in base al tipo e al numero di parametri, in modo che due funzioni con lo stesso nome abbiano nomi di simboli diversi.

Il codice all’interno di una extern "C" è ancora codice C ++. Esistono limitazioni su cosa è ansible fare in un blocco “C” esterno, ma riguardano esclusivamente il collegamento. Non è ansible 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" blocchi extern "C" esterni si annidano bene. Esiste anche extern "C++" se vi trovate irrimediabilmente intrappolati all’interno delle regioni extern "C" , ma non è una buona idea dal punto di vista della pulizia.

Ora, in particolare per quanto riguarda le vostre domande numerate:

Per quanto riguarda il n. 1: __cplusplus deve essere definito all’interno dei blocchi extern "C" esterni. Questo non importa, però, dato che i blocchi dovrebbero nidificare perfettamente.

Per quanto riguarda il n. 2: __cplusplus verrà definito per qualsiasi unità di compilazione che viene eseguita tramite il compilatore C ++. In genere, ciò significa file .cpp e qualsiasi file incluso da quel file .cpp. Lo stesso file .h (o .hh o .hpp o what-have-you) potrebbe essere interpretato come C o C ++ in momentjs 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 – da qui il #ifdef __cplusplus controllo #ifdef __cplusplus .

Per rispondere alla domanda n. 3: le funzioni senza prototipi avranno il collegamento C ++ se sono in file .cpp e non all’interno di un blocco extern "C" . Questo va bene, però, perché se non ha un prototipo, può essere chiamato solo da altre funzioni nello stesso file, e di solito non ti interessa come appare il collegamento, perché non hai intenzione di avere quella funzione essere chiamato da qualsiasi cosa al di fuori della stessa unità di compilazione comunque.

Per il # 4, hai capito esattamente. Se si include un’intestazione per il codice che presenta il collegamento C (ad esempio il codice compilato da un compilatore C), è necessario extern "C" dall’intestazione, in questo modo sarà ansible collegarsi alla libreria. (Altrimenti, il tuo linker _Z1hic funzioni con nomi come _Z1hic quando stavi cercando void h(int, char)

5: Questo tipo di missaggio è una ragione comune per usare la extern "C" , e non vedo nulla di sbagliato nel farlo in questo modo – assicurati solo di capire cosa stai facendo.

  1. extern "C" non modifica la presenza o l’assenza della macro __cplusplus . Cambia solo il collegamento e la manipolazione dei nomi delle dichiarazioni avvolte.

  2. Puoi nidificare i blocchi extern "C" molto volentieri.

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

  4. Puoi tranquillamente mischiare C e C ++ in questo modo.

Un paio di trucchi che sono i collorici dell’eccellente risposta di Andrew Shelansky e di non essere d’accordo con un po ‘ non cambia il modo in cui il compilatore legge il codice

Poiché i tuoi prototipi di funzione sono compilati come C, non è ansible sovraccaricare gli stessi nomi di funzione con parametri diversi: questa è una delle caratteristiche principali del mangling del nome del compilatore. È descritto come un problema di collegamento ma non è del tutto vero: si otterranno 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 l’overloading.

Gli errori del linker si verificheranno in seguito perché la tua funzione sembrerebbe non essere trovata, se non hai il wrapper “C” extern intorno alle dichiarazioni e l’header è incluso in una miscela di sorgenti C e C ++.

Una ragione per scoraggiare le persone dall’usare la C compilata come impostazione C ++ è perché questo significa che il loro codice sorgente non è più portatile. Quella 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 necessario per rinominare i suffissi dei file in .cpp.