Se trovi qualche dettaglio che ti fa dubitare, puoi commentarlo e ti risponderemo il prima possibile.
Soluzione:
extern "C"
fa sì che il nome di una funzione in C++ abbia un collegamento C (il compilatore non manipola il nome), in modo che il codice C client possa collegarsi (utilizzare) la vostra funzione utilizzando un file di intestazione compatibile con il C che contiene solo la dichiarazione della funzione. La definizione della funzione è contenuta in un formato binario (compilato dal compilatore C++) che il linker del client C collegherà utilizzando il nome C.
Poiché il C++ prevede l'overloading dei nomi delle funzioni e il C no, il compilatore C++ non può usare il nome della funzione come un id univoco a cui collegarsi, quindi manipola il nome aggiungendo informazioni sugli argomenti. Un compilatore C non ha bisogno di manipolare il nome, poiché in C non è possibile sovraccaricare i nomi delle funzioni. Quando si afferma che una funzione ha extern "C"
in C++, il compilatore C++ non aggiunge informazioni sul tipo di argomento/parametro al nome usato per il collegamento.
A titolo informativo, è possibile specificare extern "C"
a ogni singola dichiarazione/definizione in modo esplicito o utilizzare un blocco per raggruppare una sequenza di dichiarazioni/definizioni che devono avere un determinato collegamento:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Se vi interessano i dettagli tecnici, sono elencati nella sezione 7.5 dello standard C++03; ecco un breve riassunto (con enfasi su extern "C"
):
extern "C"
è una specifica di collegamento- Ogni compilatore è richiesto di fornire un collegamento "C
- Una specifica di collegamento deve essere presente solo nell'ambito dello spazio dei nomi
Tutti i tipi di funzione, i nomi di funzione e i nomi di variabile hanno un collegamento linguisticoVedere il Commento di Richard: Solo i nomi di funzione e i nomi di variabile con collegamento esterno hanno un collegamento linguistico- Due tipi di funzioni con collegamenti linguistici distinti sono tipi distinti anche se altrimenti identici
- Le specifiche di collegamento si annidano, quella interna determina il collegamento finale
extern "C"
è ignorato per i membri di una classe- Una funzione al massimo con un particolare nome può avere un collegamento "C" (indipendentemente dallo spazio dei nomi)
Vedere il commento di Richard:extern "C"
costringe una funzione ad avere un collegamento esterno (non può renderla statica)static
all'internoextern "C"
è valid; un'entità così dichiarata ha un collegamento interno, e quindi non ha un collegamento linguistico- Il collegamento dal C++ a oggetti definiti in altri linguaggi e a oggetti definiti in C++ da altri linguaggi è definito dall'implementazione e dipende dal linguaggio. Solo quando le strategie di layout degli oggetti delle implementazioni di due linguaggi sono sufficientemente simili è possibile ottenere tale collegamento.
Volevo solo aggiungere un'informazione, visto che non l'ho ancora vista pubblicata.
Molto spesso si vede del codice nelle intestazioni del C come questo:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
Questo permette di usare quel file di intestazione C con il codice C++, perché la macro "__cplusplus" sarà definita. Ma è possibile anche ancora utilizzarlo con il codice C legacy, dove la macro è NON definita, quindi non vedrà il costrutto univoco del C++.
Tuttavia, ho visto anche codice C++ come:
extern "C" {
#include "legacy_C_header.h"
}
che immagino realizzi più o meno la stessa cosa.
Non sono sicuro di quale sia il modo migliore, ma li ho visti entrambi.
Decompilare un g++
generato per vedere cosa sta succedendo
main.cpp
void f() {}
void g();
extern "C" {
void ef() {}
void eg();
}
/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }
Compilare e disassemblare l'output ELF generato:
g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o
L'output contiene:
8: 0000000000000000 7 FUNC GLOBAL DEFAULT 1 _Z1fv
9: 0000000000000007 7 FUNC GLOBAL DEFAULT 1 ef
10: 000000000000000e 17 FUNC GLOBAL DEFAULT 1 _Z1hv
11: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _GLOBAL_OFFSET_TABLE_
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND _Z1gv
13: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND eg
Interpretazione
Vediamo che:
-
ef
eeg
sono stati memorizzati in simboli con lo stesso nome del codice -
gli altri simboli sono stati maciullati. Vediamo di districarli:
$ c++filt _Z1fv f() $ c++filt _Z1hv h() $ c++filt _Z1gv g()
Conclusione: entrambi i seguenti tipi di simboli erano non maciullati:
- definito
- dichiarato ma non definito (
Ndx = UND
), da fornire in fase di collegamento o di esecuzione da un altro file oggetto
Quindi è necessario extern "C"
entrambi al momento della chiamata:
- C da C ++: dire
g++
di aspettarsi i simboli non manipolati prodotti dagcc
- C++ da C: dire a
g++
di generare simboli non modificati pergcc
per utilizzare
Cose che non funzionano in extern C
È ovvio che qualsiasi caratteristica del C++ che richieda la manipolazione dei nomi 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 void f(C i) { }
}
Esempio di C minimo eseguibile da C ++
Per completezza e per i neofiti, si veda anche: Come usare i file sorgente C in un progetto C++?
Chiamare il C dal C++ è piuttosto semplice: ogni funzione C ha solo un possibile simbolo non mutato, quindi non è necessario alcun lavoro aggiuntivo.
main.cpp
#include
#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++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
c.c
#include "c.h"
int f(void) { return 1; }
Esegui:
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 fallisce con:
main.cpp:6: undefined reference to `f()'
perché g++
si aspetta di trovare un file f
, che gcc
non ha prodotto.
Esempio su GitHub.
Esempio minimo di C++ eseguibile da C
Chiamare il C++ dal C è un po' più difficile: dobbiamo creare manualmente versioni non modificate di ogni funzione che vogliamo esporre.
Qui illustriamo come esporre gli overload delle funzioni C++ in C.
main.c
#include
#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);
}
Esegui:
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 maciullati che gcc
non può trovare.
Esempio su GitHub.
Dove si trova il file extern "c"
quando includo le intestazioni C dal C++?
- Le versioni C++ delle intestazioni C come
cstdio
potrebbero fare affidamento su#pragma GCC system_header
che https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html cita: "Su alcuni target, come RS/6000 AIX, GCC circonda implicitamente tutte le intestazioni di sistema con un blocco 'extern "C"' quando si compila in C++", ma non ho avuto piena conferma. - Intestazioni POSIX come
/usr/include/unistd.h
sono trattati in: È necessario un blocco extern "C" per includere intestazioni POSIX C standard? via__BEGIN_DECLS
riprodotto su Ubuntu 20.04.__BEGIN_DECLS
è incluso tramite#include
.
Testato in Ubuntu 18.04.
Sezione recensioni e valutazioni
Se questo post ti è stato utile, sarebbe molto utile se lo condividessi con più sviluppatori in questo modo ci aiuti a diffondere queste informazioni.