Domanda

Cosa fa esattamente inserendo extern " C " nel codice C ++?

Ad esempio:

extern "C" {
   void foo();
}
È stato utile?

Soluzione

extern "C" fa in modo che un nome-funzione in C ++ abbia un collegamento 'C' (il compilatore non altera il nome) in modo che il codice C del client possa collegarsi (cioè usare) la tua funzione usando un file di intestazione compatibile 'C' che contiene solo la dichiarazione della tua funzione . La definizione della funzione è contenuta in un formato binario (compilato dal compilatore C ++) che il linker "C" del client collegherà quindi utilizzando il nome "C".

Poiché C ++ ha un sovraccarico di nomi di funzioni e C no, il compilatore C ++ non può semplicemente usare il nome della funzione come ID univoco a cui collegarsi, quindi altera il nome aggiungendo informazioni sugli argomenti. Un compilatore C non ha bisogno di modificare il nome poiché non è possibile sovraccaricare i nomi delle funzioni in C. Quando si afferma che una funzione ha esternamente "C" collegamento in C ++, il compilatore C ++ non aggiunge informazioni sul tipo di argomento / parametro al nome utilizzato per il collegamento.

Solo così sai, puoi specificare " C " collegamento a ogni singola dichiarazione / definizione esplicitamente 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 della norma C ++ 03, ecco un breve riassunto (con enfasi su "C" esterno):

  • esterno "C" è una specifica di collegamento
  • Ogni compilatore è richiesto per fornire " C " legame
  • una specifica di collegamento deve essere presente solo nell'ambito dello spazio dei nomi
  • tutti i tipi di funzione, nomi di funzioni e nomi di variabili hanno un collegamento linguistico Vedi il commento di Richard: Solo i nomi delle funzioni e i nomi delle variabili con collegamento esterno hanno un collegamento linguistico
  • due tipi di funzioni con collegamenti linguistici distinti sono tipi distinti anche se altrimenti identici
  • annidamento delle specifiche del collegamento, quello interno determina il collegamento finale
  • esterno "C" è ignorato per i membri della classe
  • al massimo una funzione con un nome particolare può avere " C " collegamento (indipendentemente dallo spazio dei nomi)
  • extern " C " impone a una funzione di avere un collegamento esterno (non può renderlo statico) 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 ++ ad oggetti definiti in altre lingue e ad oggetti definiti in C ++ da altre lingue è definito dall'implementazione e dipendente dalla lingua. Solo laddove le strategie di layout degli oggetti di due implementazioni linguistiche sono abbastanza simili è possibile ottenere tale collegamento

Altri suggerimenti

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

Molto spesso vedrai il codice nelle intestazioni C in questo modo:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Ciò che questo compie è che ti consente di utilizzare quel file di intestazione C con il tuo codice C ++, perché la macro " __ cplusplus " sarà definito. Ma puoi anche usarlo ancora con il tuo codice C legacy, dove la macro è NON definita, quindi non vedrà il costrutto C ++ in modo univoco.

Anche se ho visto anche codice C ++ come:

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

che immagino realizza più o meno la stessa cosa.

Non sono sicuro di quale sia il modo 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 due funzioni non statiche possono avere lo stesso nome.

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

Quindi se specifichi una funzione come extern C, il compilatore non esegue la modifica del nome con essa e può essere direttamente accessibile usando il nome del suo simbolo come nome della funzione.

Questo è utile quando si usano dlsym () e dlopen () per chiamare tali funzioni.

Decompila un binario generato g ++ 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(); }

Compila con GCC 4.8 Linux ELF output:

g++ -c main.cpp

Decompila 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 ad esempio sono stati memorizzati in simboli con lo stesso nome del codice

  • gli altri simboli sono stati alterati. Districiamoli:

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

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

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

Quindi avrai bisogno di extern " C " quando chiami:

  • C da C ++: indica a g ++ di aspettarsi simboli non combinati prodotti da gcc
  • C ++ da C: dì a g ++ di generare simboli non confusi che gcc da usare

Cose che non funzionano in C esterno

Diventa evidente 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 nuovi arrivati, vedere anche: Come utilizzare 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 extra.

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

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 ++ prevede di trovare un f , che gcc non ha prodotto.

Esempio su GitHub / >

C ++ eseguibile minimo dall'esempio C

Chiamare C ++ da C è un po 'più difficile: dobbiamo creare manualmente versioni non alterate 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);
}

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 distorti che gcc non riesce a trovare.

Esempio su GitHub / >

Testato in Ubuntu 18.04.

C ++ mangles nomi di funzioni per creare un linguaggio orientato agli oggetti da un linguaggio procedurale

La maggior parte dei linguaggi di programmazione non è costruita sulla base dei linguaggi di programmazione esistenti. C ++ è costruito sopra C, e inoltre è un linguaggio di programmazione orientato agli oggetti costruito da un linguaggio di programmazione procedurale, e per questo motivo ci sono espressioni C ++ come extern & C; quot; che forniscono retrocompatibilità con C.

Diamo un'occhiata al seguente esempio:

#include <stdio.h>

// Two functions are defined with the same name
// but have different parameters

void printMe(int a) {
  printf("int: %i\n", a);
}

void printMe(char a) {
  printf("char: %c\n", a);
}

int main() {
  printMe("a");
  printMe(1);
  return 0;
}

Il compilatore AC non compilerà l'esempio sopra, perché la stessa funzione printMe è definita due volte (anche se hanno parametri diversi int a vs char a ).

  

gcc -o printMe printMe.c & amp; & amp; ./printMe;
   1 errore. PrintMe è definito più di una volta.

Un compilatore C ++ compilerà l'esempio sopra. Non importa che printMe sia definito due volte.

  

g ++ -o printMe printMe.c & amp; & amp; ./printMe;

Questo perché un compilatore C ++ rinomina implicitamente ( mangles ) funzioni in base ai loro parametri. In C, questa funzione non era supportata. Tuttavia, quando C ++ è stato creato su C, il linguaggio è stato progettato per essere orientato agli oggetti e doveva supportare la capacità di creare classi diverse con metodi (funzioni) con lo stesso nome e di sovrascrivere metodi ( override del metodo ) basato su parametri diversi.

extern " C " dice " non manipolare i nomi delle funzioni C "

Tuttavia, immagina di avere un file C legacy chiamato " parent.c " che include i nomi delle funzioni di da altri file C legacy, "parent.h", "child.h", ecc. Se l'eredità "parent.c" il file viene eseguito attraverso un compilatore C ++, quindi i nomi delle funzioni verranno alterati e non corrisponderanno più ai nomi delle funzioni specificati in " parent.h " ;, " child.h " ;, ecc., quindi i nomi delle funzioni in quelle esterne i file dovrebbero anche essere alterati. La modifica dei nomi delle funzioni in un programma C complesso, quelli con molte dipendenze, può portare a un codice non funzionante; quindi potrebbe essere conveniente fornire una parola chiave che indichi al compilatore C ++ di non modificare il nome di una funzione.

La parola chiave extern & C; quot; indica a un compilatore C ++ di non manipolare (rinominare) i nomi delle funzioni C. Esempio di utilizzo: extern " C " void printMe (int a);

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 è mangled .

Non è possibile rendere compatibile alcun C-header con C ++ semplicemente racchiudendo "C" esterno. Quando gli identificatori in un header C entrano in conflitto con le parole chiave C ++, il compilatore C ++ si lamenterà di questo.

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

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

Kinda ha senso, ma è qualcosa da tenere a mente quando si esegue il porting del codice C in C ++.

Informa il compilatore C ++ di 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.

extern "C" deve essere riconosciuto da un compilatore C ++ e notificare al compilatore che la funzione nota è (o deve essere) compilata in stile C. In modo che durante il collegamento, si collega alla versione corretta della funzione da C.

Ho usato "extern" C " " prima che i file dll (libreria a collegamento dinamico) rendessero ecc. funzione main () "esportabile" così può essere usato in seguito 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);
}

extern " C " è una specifica di collegamento che viene utilizzata per chiamare le funzioni C nei file sorgente Cpp . Possiamo chiamare funzioni C, scrivere variabili e & amp; includi le intestazioni . La funzione è dichiarata in entità esterna & amp; è definito all'esterno. La sintassi è

Tipo 1:

extern "language" function-prototype

Tipo 2:

extern "language"
{
     function-prototype
};

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

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

  • in C ++, puoi avere lo stesso nome in classe tramite sovraccarico (ad esempio, poiché sono tutti dello stesso nome non possono essere esportati così come sono dalla dll, ecc.) la soluzione a questi problemi è che vengono convertiti in stringhe diverse (chiamati simboli), simboli indica il nome della funzione, anche gli argomenti, quindi ognuna di queste funzioni, anche con lo stesso nome, può essere identificata in modo univoco (anche chiamata, manipolazione dei nomi)
  • 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 stessa)

Quindi
in C ++, con il nome che modifica le identità in modo univoco per ciascuna funzione
in C, anche senza che il nome modifichi in modo univoco le identità per ciascuna funzione

Per modificare il comportamento di C ++, ovvero specificare che la modifica del nome non dovrebbe avvenire per una particolare funzione, è possibile utilizzare extern & C; quot &

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

Quando si mescolano C e C ++ (ovvero, a. chiamando la funzione C da C ++; e b. chiamando la funzione C ++ da C), la modifica del nome C ++ causa problemi di collegamento. Tecnicamente parlando, questo problema si verifica solo quando le funzioni di chiamata sono già state compilate in binario (molto probabilmente, un file di libreria * .a) usando il compilatore corrispondente.

Quindi dobbiamo usare extern " C " per disabilitare la modifica del nome in C ++.

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