[c++] Qual è l'effetto di extern "C" in C ++?



Answers

Volevo solo aggiungere un po 'di informazioni, dato che non l'ho ancora visto pubblicato.

Vedrai molto spesso il codice in intestazioni C in questo modo:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Ciò che compie è che ti permette di usare quel file di intestazione C con il tuo codice C ++, perché verrà definita la macro "__cplusplus". Ma puoi anche usarlo con il tuo codice C legacy, dove la macro NON è definita, quindi non vedrà il costrutto C ++ in modo univoco.

Sebbene, ho anche visto il codice C ++ come:

extern "C" {
#include "legacy_C_header.h"
}

che immagino compia più o meno la stessa cosa

Non sono sicuro quale sia la via migliore, ma ho visto entrambi.

Question

Che cosa fa esattamente la extern "C" in codice C ++?

Per esempio:

extern "C" {
   void foo();
}



Nessun C-header verrà compilato con extern "C". Quando gli identificatori in un conflitto C-header con parole chiave C ++, il compilatore C ++ si lamenterà di ciò.

Ad esempio, ho visto il seguente codice fallire in un g ++:

extern "C" {
struct method {
    int virtual;
};
}

Un po 'ha senso, ma è qualcosa da tenere a mente quando si porta il codice C in C ++.




Ho usato "extern" C "prima per i file dll (dynamic link library) per rendere ecc. La funzione main ()" esportabile "in modo che possa essere utilizzata successivamente in un altro eseguibile da dll. Forse un esempio di dove lo usavo può essere utile.

DLL

#include <string.h>
#include <windows.h>

using namespace std;

#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
    MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}

EXE

#include <string.h>
#include <windows.h>

using namespace std;

typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder

int main()
{
    char winDir[MAX_PATH];//will hold path of above dll
    GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
    strcat(winDir,"\\exmple.dll");//concentrate dll name with path
    HINSTANCE DLL = LoadLibrary(winDir);//load example dll
    if(DLL==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if load fails exit
        return 0;
    }
    mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
    //defined variable is used to assign a function from dll
    //GetProcAddress is used to locate function with pre defined extern name "DLL"
    //and matcing function name
    if(mainDLLFunc==NULL)
    {
        FreeLibrary((HMODULE)DLL);//if it fails exit
        return 0;
    }
    mainDLLFunc();//run exported function 
    FreeLibrary((HMODULE)DLL);
}



Decompiliamo il file oggetto g ++ generato per vedere cosa succede all'interno di questa implementazione.

Genera esempio

Ingresso:

void f() {}
void g();

extern "C" {
    void ef() {}
    void eg();
}

/* Prevent g and eg from being optimized away. */
void h() { g(); eg(); }

Compilare con l'output ELF di GCC 4.8 Linux:

g++ -c a.cpp

Decompila la tabella dei simboli:

readelf -s a.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 sono stati mutilati. Mistriamoli:

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

Conclusione: entrambi i seguenti tipi di simboli non sono stati alterati:

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

Quindi avrai bisogno di una extern "C" sia quando chiamerai:

  • C da C ++: indica a g++ di aspettarsi simboli non complicati prodotti da gcc
  • C ++ da C: tell g++ per generare simboli senza maglie per gcc da usare

Cose che non funzionano nell'esterno C

Diventa ovvio che qualsiasi funzionalità di C ++ che richiede un nome di manomissione non verrà eseguita 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) { }
}



Quando si mischiano C e C ++ (ad esempio, una chiamata alla funzione C da C ++ e b chiamando la funzione C ++ da C), il nome del linguaggio C ++ causa problemi di collegamento. Tecnicamente parlando, questo problema si verifica solo quando le funzioni del callee sono già state compilate in binario (molto probabilmente, un file di libreria * .a) utilizzando il compilatore corrispondente.

Quindi abbiamo bisogno di usare "C" extern per disabilitare il nome mangling in C ++.




Informa il compilatore C ++ per cercare i nomi di quelle funzioni in uno stile C durante il collegamento, perché i nomi delle funzioni compilate in C e C ++ sono diversi durante la fase di collegamento.




Related