c++ example - Qual è l'effetto di extern "C" in C ++?





significato windows (12)


Decompila un file binario generato da g++ per vedere cosa sta succedendo

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) { }
}

C minima eseguibile dall'esempio C ++

Per motivi di completezza e per i newbs là fuori.

Chiamare C da C ++ è piuttosto semplice: ogni funzione C ha solo un possibile simbolo non mutilato, quindi non è richiesto alcun lavoro extra.

main.cpp:

#include <cassert>

#include "c.h"

int main() {
    assert(f() == 1);
}

ch:

#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

cc:

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

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

perché g++ aspetta di trovare un f mangled, che gcc non ha prodotto.

Esempio su GitHub .

C ++ eseguibile minimo nell'esempio C.

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

Qui illustriamo come esporre gli overload di funzioni 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++ generato simboli storpiati che gcc non riesce a trovare.

Esempio su GitHub .

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




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.




Cambia il collegamento di una funzione in modo tale che la funzione sia richiamabile da C. In pratica ciò significa che il nome della funzione non viene mangled .




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);
}



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




Questa risposta è per gli impazienti / hanno scadenze da rispettare, solo una parte / spiegazione semplice è qui sotto:

  • in C ++, puoi avere lo stesso nome in classe tramite sovraccarico (ad esempio, dal momento che sono tutti lo stesso nome non può essere esportato come-è da dll, ecc.) la soluzione a questi problemi è che vengono convertiti in stringhe diverse (chiamate simboli ), i simboli rappresentano il nome della funzione, anche gli argomenti, quindi ognuna di queste funzioni, anche con lo stesso nome, può essere identificata in modo univoco (anche chiamato, nome mangling)
  • in C, non hai sovraccarico, il nome della funzione è univoco (quindi non è richiesta una stringa separata per identificare un nome di funzione in modo univoco, quindi il simbolo è il nome della funzione stesso)

Così
in C ++, con il nome che modifica ogni identità in modo univoco ogni funzione
in C, anche senza nome, manipolando identità univoche ogni funzione

Per cambiare il comportamento del C ++, cioè, per specificare che il nome mangling non dovrebbe accadere per una particolare funzione, puoi usare extern "C" prima del nome della funzione, per qualsiasi ragione, come esportare una funzione con un nome specifico da una dll , per l'utilizzo da parte dei suoi clienti.

Leggi altre risposte, per risposte più dettagliate / più corrette.




extern "C" è una specifica di collegamento che viene utilizzata per chiamare le funzioni C nei file di origine Cpp . Possiamo chiamare funzioni C, scrivere variabili e includere intestazioni . La funzione è dichiarata nell'entità esterna e viene definita all'esterno. La sintassi è

Tipo 1:

extern "language" function-prototype

Tipo 2:

extern "language"
{
     function-prototype
};

per esempio:

#include<iostream>
using namespace std;

extern "C"
{
     #include<stdio.h>    // Include C Header
     int n;               // Declare a Variable
     void func(int,int);  // Declare a function (function prototype)
}

int main()
{
    func(int a, int b);   // Calling function . . .
    return 0;
}

// Function definition . . .
void func(int m, int n)
{
    //
    //
}



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.




In ogni programma C ++, tutte le funzioni non statiche sono rappresentate nel file binario come simboli. Questi simboli sono stringhe di testo speciali che identificano in modo univoco una funzione nel programma.

In C, il nome del simbolo è uguale al nome della funzione. Questo è possibile perché in C non esistono due funzioni non statiche che possono avere lo stesso nome.

Poiché C ++ consente il sovraccarico e ha molte caratteristiche che C non ha - come classi, funzioni membro, specifiche di eccezione - non è possibile usare semplicemente il nome della funzione come nome del simbolo. Per risolvere ciò, C ++ usa il cosiddetto mangling del nome, che trasforma il nome della funzione e tutte le informazioni necessarie (come il numero e la dimensione degli argomenti) in una strana stringa elaborata solo dal compilatore e dal linker.

Quindi se si specifica una funzione come extern C, il compilatore non esegue il nome di mangling con esso e si può accedere direttamente usando il suo nome simbolico come nome della funzione.

Questo è utile durante l'utilizzo di dlsym() e dlopen() per chiamare tali funzioni.




extern "C" fa un nome-funzione in C ++ ha un link "C" (il compilatore non manipola il nome) in modo che il codice C client possa collegarsi (cioè utilizzare) la propria funzione usando un file di intestazione compatibile "C" che contiene solo il dichiarazione della tua funzione. La definizione della tua funzione è contenuta in un formato binario (che è stato compilato dal compilatore C ++) al quale verrà collegato il linker "C" del client utilizzando il nome "C".

Dato che C ++ ha un sovraccarico dei nomi delle funzioni e C no, il compilatore C ++ non può semplicemente usare il nome della funzione come un ID univoco a cui collegarsi, in modo da manomettere il nome aggiungendo informazioni sugli argomenti. Il compilatore AC non ha bisogno di manipolare il nome dato che non puoi sovraccaricare i nomi delle funzioni in C. Quando dichiari che una funzione ha un link "C" extern in C ++, il compilatore C ++ non aggiunge argomento / tipo di parametro informazioni al nome usato per linkage.

Solo per quello che sai, puoi specificare esplicitamente il collegamento "C" per ogni singola dichiarazione / definizione o utilizzare un blocco per raggruppare una sequenza di dichiarazioni / definizioni per avere un determinato collegamento:

extern "C" void foo(int);
extern "C"
{
   void g(char);
   int i;
}

Se ti interessano i tecnicismi, 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 è tenuto a fornire il collegamento "C"
  • una specifica di collegamento deve avvenire solo nell'ambito dello spazio dei nomi
  • tutti i tipi di funzione, i nomi delle funzioni e i nomi delle variabili hanno un collegamento linguistico. Vedere il commento di Richard: solo i nomi delle funzioni e i nomi delle variabili con collegamento esterno hanno un collegamento linguistico
  • due tipi di funzione con collegamenti linguistici distinti sono tipi distinti anche se altrimenti identici
  • nidificazione delle specifiche di collegamento, quella interna determina il collegamento finale
  • extern "C" viene ignorata per i membri della classe
  • al massimo una funzione con un nome particolare può avere il collegamento "C" (indipendentemente dallo spazio dei nomi)
  • la "C" esterna obbliga una funzione ad avere un collegamento esterno (non può renderla statica) Vedi il commento di Richard: "statico" dentro "extern" C "" è valido; un'entità così dichiarata ha un collegamento interno e quindi non ha un collegamento linguistico
  • Il collegamento da C ++ agli oggetti definiti in altri linguaggi e agli oggetti definiti in C ++ da altre lingue è definito dall'implementazione e dipendente dalla lingua. Solo laddove le strategie di layout degli oggetti delle implementazioni di due lingue sono abbastanza simili, è possibile ottenere tale collegamento



??! è un trigraph che si traduce in | . Quindi dice:

!ErrorHasOccured() || HandleError();

che, a causa del cortocircuito, equivale a:

if (ErrorHasOccured())
    HandleError();

Guru della settimana (si occupa di C ++ ma è pertinente qui), dove ho scelto questo.

Possibile origine dei trigrafi o come @DwB indica nei commenti è più probabile che a causa di EBCDIC sia difficile (di nuovo). This discussione sulla scheda IBM developerworks sembra supportare questa teoria.

Da ISO / IEC 9899: 1999 §5.2.1.1, nota 12 (h / t @ Random832):

Le sequenze del trigramma consentono l'immissione di caratteri che non sono definiti nel set di codici invarianti come descritto in ISO / IEC 646, che è un sottoinsieme del set di codici ASCII US a sette bit.







c++ c linkage name-mangling extern-c