Perché dobbiamo usare:
extern "C" {
#include <foo.h>
}
In particolare:
Quando dovremmo usarlo?
Cosa sta succedendo a livello di compilatore/linker che ci richiede di usarlo?
Come in termini di compilazione/collegamento questo risolve i problemi che ci richiedono di usarlo?
C e C++ sono superficialmente simili, ma ognuno si compila in un set di codice molto diverso. Quando si include un file di intestazione con un compilatore C++, il compilatore si aspetta il codice C++. Se, tuttavia, si tratta di un'intestazione C, il compilatore si aspetta che i dati contenuti nel file di intestazione vengano compilati in un determinato formato: "ABI" C++ o "Application Binary Interface", quindi il linker si blocca. Ciò è preferibile al passaggio di dati C++ a una funzione in attesa di dati C.
(Per entrare nel nocciolo della realtà, l'ABI di C++ generalmente 'manipola' i nomi delle loro funzioni/metodi, quindi chiamando printf()
senza contrassegnare il prototipo come funzione C, il C++ genererà effettivamente il codice chiamando _Zprintf
, più cazzate extra alla fine.)
Quindi: usa extern "C" {...}
quando si include un'intestazione c: è così semplice. Altrimenti, avrai una discrepanza nel codice compilato e il linker si strozzerà. Per la maggior parte delle intestazioni, tuttavia, non sarà nemmeno necessario il extern
poiché la maggior parte delle intestazioni del sistema C considererà già il fatto che potrebbero essere incluse nel codice C++ e già extern
il loro codice.
extern "C" determina come devono essere denominati i simboli nel file oggetto generato. Se una funzione viene dichiarata senza "C" esterno, il nome del simbolo nel file oggetto utilizzerà la modifica del nome C++. Ecco un esempio.
Dato test.C in questo modo:
void foo() { }
Compilare ed elencare i simboli nel file oggetto fornisce:
$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
U __gxx_personality_v0
La funzione foo è in realtà chiamata "_Z3foov". Questa stringa contiene informazioni sul tipo per il tipo e i parametri di ritorno, tra le altre cose. Se invece scrivi test.C in questo modo:
extern "C" {
void foo() { }
}
Quindi compilare e guardare i simboli:
$ g++ -c test.C
$ nm test.o
U __gxx_personality_v0
0000000000000000 T foo
Ottieni il collegamento C. Il nome della funzione "pippo" nel file oggetto è solo "pippo" e non ha tutte le informazioni sul tipo di fantasia che derivano dalla modifica del nome.
Generalmente includi un'intestazione all'interno di "C" {} esterno se il codice che lo accompagna è stato compilato con un compilatore C ma stai provando a chiamarlo da C++. Quando lo fai, stai dicendo al compilatore che tutte le dichiarazioni nell'intestazione useranno il collegamento C. Quando colleghi il tuo codice, i tuoi file .o conterranno riferimenti a "pippo", non a "_Z3fooblah", che si spera corrisponda a qualsiasi cosa ci sia nella libreria a cui stai collegando.
La maggior parte delle biblioteche moderne metterà le protezioni attorno a tali intestazioni in modo che i simboli vengano dichiarati con il giusto collegamento. per esempio. in molte intestazioni standard troverai:
#ifdef __cplusplus
extern "C" {
#endif
... declarations ...
#ifdef __cplusplus
}
#endif
Questo si assicura che quando il codice C++ include l'intestazione, i simboli nel file oggetto corrispondono a quelli presenti nella libreria C. Dovresti solo mettere "C" esterno {} attorno alla tua intestazione C se è vecchio e non ha già queste protezioni.
In C++, puoi avere diverse entità che condividono un nome. Ad esempio, ecco un elenco di funzioni tutte denominate pippo:
A::foo()
B::foo()
C::foo(int)
C::foo(std::string)
Al fine di differenziare tutti, il compilatore C++ creerà nomi univoci per ciascuno di essi in un processo chiamato name-mangling o decorating. I compilatori C non lo fanno. Inoltre, ogni compilatore C++ può farlo in un modo diverso.
extern "C" dice al compilatore C++ di non eseguire alcuna modifica del nome sul codice tra parentesi graffe. Ciò consente di chiamare le funzioni C dall'interno di C++.
Ha a che fare con il modo in cui i diversi compilatori eseguono la manipolazione del nome. Un compilatore C++ altererà il nome di un simbolo esportato dal file di intestazione in un modo completamente diverso rispetto a un compilatore C, quindi quando si tenta di collegarsi, si otterrebbe un errore del linker che dice che mancavano i simboli.
Per risolvere questo, diciamo al compilatore C++ di funzionare in modalità "C", quindi esegue la modifica del nome allo stesso modo del compilatore C. Fatto ciò, gli errori del linker sono stati corretti.
Quando dovremmo usarlo?
Quando si collegano C libaries in file di oggetti C++
Cosa sta succedendo a livello di compilatore/linker che ci richiede di usarlo?
C e C++ utilizzano schemi diversi per la denominazione dei simboli. Questo dice al linker di usare lo schema di C quando si collega nella libreria data.
Come in termini di compilazione/collegamento questo risolve i problemi che ci richiedono di usarlo?
L'uso dello schema di denominazione C consente di fare riferimento a simboli in stile C. Altrimenti il linker proverebbe simboli in stile C++ che non funzionerebbero.
C e C++ hanno regole diverse sui nomi dei simboli. I simboli sono il modo in cui il linker sa che la chiamata alla funzione "openBankAccount" in un file oggetto prodotta dal compilatore è un riferimento a quella funzione che hai chiamato "openBankAccount" in un altro file oggetto prodotto da un altro file sorgente dallo stesso (o compatibile) compilatore. Ciò consente di creare un programma da più di un file sorgente, il che è un sollievo quando si lavora su un grande progetto.
In C la regola è molto semplice, i simboli sono comunque tutti in un unico spazio dei nomi. Pertanto, l'intero "socks" viene memorizzato come "socks" e la funzione count_socks viene memorizzata come "count_socks".
I linker sono stati creati per C e altri linguaggi come C con questa semplice regola di denominazione dei simboli. Quindi i simboli nel linker sono solo stringhe semplici.
Ma in C++ il linguaggio ti consente di avere spazi dei nomi, polimorfismo e varie altre cose che sono in conflitto con una regola così semplice. Tutte e sei le funzioni polimorfiche chiamate "aggiungi" devono avere simboli diversi, altrimenti verrà utilizzato quello sbagliato da altri file oggetto. Questo viene fatto "manipolando" (che è un termine tecnico) i nomi dei simboli.
Quando si collega il codice C++ alle librerie o al codice C, è necessario esternamente "C" qualsiasi cosa scritta in C, come file di intestazione per le librerie C, per dire al compilatore C++ che questi nomi di simboli non devono essere alterati, mentre il resto di il tuo codice C++ ovviamente deve essere alterato o non funzionerà.
Il compilatore C++ crea nomi di simboli in modo diverso rispetto al compilatore C. Pertanto, se si sta tentando di effettuare una chiamata a una funzione che risiede in un file C, compilato come codice C, è necessario indicare al compilatore C++ che i nomi dei simboli che sta tentando di risolvere sembrano diversi da quelli predefiniti; altrimenti il passaggio del collegamento fallirà.
Dovresti usare la "C" esterna ogni volta che includi un'intestazione che definisce le funzioni che risiedono in un file compilato da un compilatore C, usato in un file C++. (Molte librerie C standard possono includere questo controllo nelle loro intestazioni per renderlo più semplice per lo sviluppatore)
Ad esempio, se si dispone di un progetto con 3 file, util.c, util.h e main.cpp e entrambi i file .c e .cpp vengono compilati con il compilatore C++ (g ++, cc, ecc.), Allora non è ' è davvero necessario e può persino causare errori del linker. Se il tuo processo di compilazione utilizza un normale compilatore C per util.c, dovrai utilizzare "C" esterno quando includi util.h.
Quello che sta succedendo è che C++ codifica i parametri della funzione nel suo nome. Ecco come funziona il sovraccarico delle funzioni. Tutto ciò che tende a succedere in una funzione C è l'aggiunta di un trattino basso ("_") all'inizio del nome. Senza usare la "C" esterna, il linker cercherà una funzione chiamata DoSomething @@ int @ float () quando il nome effettivo della funzione è _DoSomething () o solo DoSomething ().
L'uso di "C" esterno risolve il problema sopra indicato dicendo al compilatore C++ che dovrebbe cercare una funzione che segue la convenzione di denominazione C invece di quella C++.
Il extern "C" {}
construct indica al compilatore di non eseguire mangling sui nomi dichiarati tra parentesi graffe. Normalmente, il compilatore C++ "migliora" i nomi delle funzioni in modo da codificare le informazioni sul tipo relative agli argomenti e al valore restituito; questo si chiama nome alterato. Il extern "C"
il costrutto impedisce la distruzione.
Viene generalmente utilizzato quando il codice C++ deve chiamare una libreria in linguaggio C. Può anche essere usato quando si espone una funzione C++ (da una DLL, ad esempio) ai client C.
Viene utilizzato per risolvere i problemi di modifica del nome. extern C significa che le funzioni si trovano in un'API di tipo C "piatta".
Decompila un g++
generato binario per vedere cosa sta succedendo
Mi sto muovendo in questa risposta da: Qual è l'effetto di "C" esterno in C++? poiché quella domanda è stata considerata una copia di questa.
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Compila con GCC 4.8 Linux ELF output:
g++ -c main.cpp
Decompilare la tabella dei simboli:
readelf -s main.o
L'output contiene:
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 6 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000006 6 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000c 16 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Interpretazione
Lo vediamo:
ef
e eg
sono stati memorizzati in simboli con lo stesso nome del codice
gli altri simboli erano mutilati. Districiamoli:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Conclusione: entrambi i seguenti tipi di simboli erano no alterati:
Ndx = UND
), da fornire al collegamento o in fase di esecuzione da un altro file oggettoQuindi avrai bisogno di extern "C"
entrambi quando si chiama:
g++
si aspettano simboli non distorti prodotti da gcc
g++
per generare simboli non combinati da utilizzare per gcc
Cose che non funzionano in C esterno
Diventa ovvio che qualsiasi funzione C++ che richiede la modifica del nome non funzionerà all'interno di 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) { }
}
Eseguibile minimo C dall'esempio C++
Per completezza e per i neofiti là fuori, vedi anche: Come usare i file sorgente C in un progetto C++?
Chiamare C da C++ è abbastanza semplice: ogni funzione C ha solo un possibile simbolo non distorto, quindi non è necessario alcun lavoro aggiuntivo.
main.cpp
#include <cassert>
#include "c.h"
int main() {
assert(f() == 1);
}
c.h
#ifndef C_H
#define C_H
/* This ifdef allows the header to be used from both C and C++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
ç.ç
#include "c.h"
int f(void) { return 1; }
Correre:
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
Senza extern "C"
il collegamento non riesce con:
main.cpp:6: undefined reference to `f()'
perché g++
si aspetta di trovare un f
rovinato, che gcc
non ha prodotto.
Esempio C++ eseguibile minimo da C
Chiamare C++ da è un po 'più difficile: dobbiamo creare manualmente versioni non distorte di ogni funzione che vogliamo esporre.
Qui illustriamo come esporre i sovraccarichi della funzione 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);
}
Correre:
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
Senza extern "C"
fallisce con:
main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'
perché g++
ha generato simboli distorti che gcc
non riesce a trovare.
Testato su Ubuntu 18.04.