it-swarm.it

Perché abbiamo bisogno di "C" esterno {#include <foo.h>} in C ++?

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?

133
Landon

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.

121
duane

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.

108
Todd Gamblin

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++.

21
Trent

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.

14
1800 INFORMATION

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.

11
Tony M

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à.

10
tialaramex

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à.

7
mbyrne215

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++.

7
HitScan

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.

6
Paul Lalonde

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".

5
Eric Z Beard

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:

  • definito
  • dichiarata ma non definita (Ndx = UND), da fornire al collegamento o in fase di esecuzione da un altro file oggetto

Quindi avrai bisogno di extern "C" entrambi quando si chiama:

  • C da C++: dire g++ si aspettano simboli non distorti prodotti da gcc
  • C++ da C: dire 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 su GitHub .

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.

Esempio su GitHub .

Testato su Ubuntu 18.04.