Domanda

Perché dobbiamo usare:

extern "C" {
#include <foo.h>
}

Nello specifico:

  • Quando dovremmo usarlo?

  • Cosa sta succedendo a livello di compilatore/linker che ci richiede di usarlo?

  • In che modo, in termini di compilazione/collegamento, questo risolve i problemi che ne richiedono l'utilizzo?

È stato utile?

Soluzione

C e C++ sono superficialmente simili, ma ciascuno viene compilato in un insieme di codice molto diverso.Quando includi un file di intestazione con un compilatore C++, il compilatore prevede codice C++.Se, tuttavia, si tratta di un'intestazione C, il compilatore si aspetta che i dati contenuti nel file di intestazione siano compilati in un determinato formato: "ABI" C++ o "Interfaccia binaria dell'applicazione", quindi il linker si blocca.Ciò è preferibile al passaggio di dati C++ a una funzione che prevede dati C.

(Per entrare nel nocciolo della questione, l'ABI di C++ generalmente "modella" i nomi delle loro funzioni/metodi, quindi chiamandoli printf() senza contrassegnare il prototipo come funzione C, il C++ genererà effettivamente chiamate di codice _Zprintf, più altra schifezza alla fine.)

COSÌ:utilizzo extern "C" {...} quando includi un'intestazione c: è semplicissimo.Altrimenti, si avrà una mancata corrispondenza nel codice compilato e il linker si bloccherà.Per la maggior parte delle intestazioni, tuttavia, non avrai nemmeno bisogno del file extern perché la maggior parte delle intestazioni del sistema C terranno già conto del fatto che potrebbero essere incluse dal codice C++ e già extern il loro codice.

Altri suggerimenti

extern "C" determina come devono essere nominati i simboli nel file oggetto generato.Se una funzione viene dichiarata senza extern "C", il nome del simbolo nel file oggetto utilizzerà la modifica dei nomi C++.Ecco un esempio.

Dato test.C in questo modo:

void foo() { }

La compilazione e l'elenco dei simboli nel file oggetto fornisce:

$ g++ -c test.C
$ nm test.o
0000000000000000 T _Z3foov
                 U __gxx_personality_v0

La funzione foo in realtà si chiama "_Z3foov".Questa stringa contiene, tra le altre cose, informazioni sul tipo restituito e sui parametri.Se invece scrivi test.C in questo modo:

extern "C" {
    void foo() { }
}

Quindi compila e guarda i simboli:

$ g++ -c test.C
$ nm test.o
                 U __gxx_personality_v0
0000000000000000 T foo

Ottieni il collegamento C.Il nome della funzione "foo" nel file oggetto è semplicemente "foo" e non contiene tutte le informazioni fantasiose che derivano dalla manipolazione dei nomi.

Generalmente includi un'intestazione all'interno di extern "C" {} 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 utilizzeranno il collegamento C.Quando colleghi il tuo codice, i tuoi file .o conterranno riferimenti a "foo", non a "_Z3fooblah", che si spera corrisponda a qualunque cosa sia presente nella libreria a cui ti stai collegando.

La maggior parte delle librerie moderne metterà delle 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

Ciò garantisce che quando il codice C++ include l'intestazione, i simboli nel file oggetto corrispondano a quelli contenuti nella libreria C.Dovresti inserire extern "C" {} attorno all'intestazione C solo se è vecchia e non ha già queste guardie.

In C++ puoi avere diverse entità che condividono un nome.Ad esempio, ecco un elenco di funzioni tutte nominate pippo:

  • A::foo()
  • B::foo()
  • C::foo(int)
  • C::foo(std::string)

Per differenziarli tutti, il compilatore C++ creerà nomi univoci per ciascuno in un processo chiamato modifica dei nomi o decorazione.I compilatori C non lo fanno.Inoltre, ogni compilatore C++ può farlo in un modo diverso.

extern "C" indica al compilatore C++ di non eseguire alcuna modifica del nome sul codice tra parentesi graffe.Ciò consente di chiamare funzioni C dall'interno di C++.

Ha a che fare con il modo in cui i diversi compilatori eseguono la modifica dei nomi.Un compilatore C++ modificherà il nome di un simbolo esportato dal file di intestazione in un modo completamente diverso rispetto a un compilatore C, quindi quando provi a collegarti, riceverai un errore del linker che dice che mancano simboli.

Per risolvere questo problema, diciamo al compilatore C++ di essere eseguito in modalità "C", quindi esegue la modifica dei nomi nello stesso modo in cui farebbe il compilatore C.Fatto ciò, gli errori del linker vengono corretti.

Quando dovremmo usarlo?

Quando si collegano librerie C a file oggetto 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 alla libreria data.

In che modo in termini di compilazione/collegamento questo risolve i problemi che ci richiedono di usarlo?

L'utilizzo 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 prodotto dal compilatore è un riferimento a quella funzione chiamata "openBankAccount" in un altro file oggetto prodotto da un file sorgente diverso 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 progetto di grandi dimensioni.

In C la regola è molto semplice, i simboli sono comunque tutti in un unico spazio dei nomi.Quindi il numero 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 semplici stringhe.

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 quello sbagliato verrà utilizzato da altri file oggetto.Questo viene fatto "movimentando" (è un termine tecnico) i nomi dei simboli.

Quando si collega il codice C++ alle librerie o al codice C, è necessario che extern "C" qualsiasi cosa scritta in C, come i 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à.

Dovresti utilizzare extern "C" ogni volta che includi un'intestazione che definisce le funzioni che risiedono in un file compilato da un compilatore C, utilizzato 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 hai un progetto con 3 file, util.c, util.h e main.cpp ed entrambi i file .c e .cpp sono compilati con il compilatore C++ (g++, cc, ecc.), allora non lo è Non è realmente necessario e potrebbe persino causare errori del linker.Se il tuo processo di compilazione utilizza un normale compilatore C per util.c, dovrai utilizzare extern "C" quando includi util.h.

Ciò che accade è che il C++ codifica i parametri della funzione nel suo nome.Ecco come funziona il sovraccarico delle funzioni.Tutto ciò che accade ad una funzione C è l'aggiunta di un carattere di sottolineatura ("_") all'inizio del nome.Senza utilizzare extern "C" il linker cercherà una funzione denominata DoSomething@@int@float() quando il nome effettivo della funzione è _DoSomething() o semplicemente DoSomething().

L'utilizzo di extern "C" risolve il problema precedente dicendo al compilatore C++ che dovrebbe cercare una funzione che segua la convenzione di denominazione C anziché quella C++.

Il compilatore C++ crea nomi di simboli in modo diverso rispetto al compilatore C.Quindi, se stai tentando di effettuare una chiamata a una funzione che risiede in un file C, compilato come codice C, devi dire al compilatore C++ che i nomi dei simboli che sta tentando di risolvere hanno un aspetto diverso da quello predefinito;in caso contrario, il passaggio del collegamento fallirà.

IL extern "C" {} Il costrutto indica al compilatore di non eseguire manipolazioni sui nomi dichiarati tra parentesi graffe.Normalmente, il compilatore C++ "migliora" i nomi delle funzioni in modo che codifichino informazioni sul tipo sugli argomenti e sul valore restituito;questo è chiamato il nome storpiato.IL extern "C" costrutto impedisce la mutilazione.

Viene in genere utilizzato quando il codice C++ deve chiamare una libreria del linguaggio C.Può anche essere utilizzato quando si espone una funzione C++ (da una DLL, ad esempio) ai client C.

Viene utilizzato per risolvere i problemi di modifica dei nomi.extern C significa che le funzioni sono in un'API "piatta" in stile C.

Decompilare a g++ binario generato per vedere cosa sta succedendo

Mi muovo in questa risposta da: Qual è l'effetto dell'extern "C" in C++? poiché quella domanda era considerata un duplicato 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(); }

Compilare con GCC 4.8 Linux ELFO produzione:

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.Scomponiamoli:

    $ c++filt _Z1fv
    f()
    $ c++filt _Z1hv
    h()
    $ c++filt _Z1gv
    g()
    

Conclusione:entrambi i seguenti tipi di simboli erano non mutilato:

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

Quindi avrai bisogno extern "C" entrambi quando chiamano:

  • C da C++:raccontare g++ aspettarsi simboli non alterati prodotti da gcc
  • C++ da C:raccontare g++ per generare simboli non alterati per gcc usare

Cose che non funzionano in C esterno

Diventa ovvio che qualsiasi funzionalità C++ che richieda la modifica dei nomi non funzionerà all'interno 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) { }
}

Esempio C eseguibile minimo da C++

Per completezza e per le novità in circolazione, vedere anche: Come utilizzare i file sorgente C in un progetto C++?

Chiamare C da C++ è piuttosto semplice:ogni funzione C ha solo un possibile simbolo non alterato, quindi non è richiesto 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

c.c

#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 fallisce con:

main.cpp:6: undefined reference to `f()'

Perché g++ si aspetta di trovarne uno mutilato f, Quale gcc non ha prodotto.

Esempio su GitHub.

C++ eseguibile minimo dall'esempio di C

Chiamare C++ da è un po' più difficile:dobbiamo creare manualmente versioni non alterate di ogni funzione che vogliamo esporre.

Qui viene illustrato come esporre gli sovraccarichi delle funzioni C++ a C.

principale.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++ generato simboli mutilati che gcc non può trovare.

Esempio su GitHub.

Testato su Ubuntu 18.04.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top