Skip to content

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

Se trovi qualche dettaglio che ti fa dubitare, puoi commentarlo e ti risponderemo il prima possibile.

Soluzione:

extern "C" fa sì che il nome di una funzione in C++ abbia un collegamento C (il compilatore non manipola il nome), in modo che il codice C client possa collegarsi (utilizzare) la vostra funzione utilizzando un file di intestazione compatibile con il C che contiene solo la dichiarazione della funzione. La definizione della funzione è contenuta in un formato binario (compilato dal compilatore C++) che il linker del client C collegherà utilizzando il nome C.

Poiché il C++ prevede l'overloading dei nomi delle funzioni e il C no, il compilatore C++ non può usare il nome della funzione come un id univoco a cui collegarsi, quindi manipola il nome aggiungendo informazioni sugli argomenti. Un compilatore C non ha bisogno di manipolare il nome, poiché in C non è possibile sovraccaricare i nomi delle funzioni. Quando si afferma che una funzione ha extern "C" in C++, il compilatore C++ non aggiunge informazioni sul tipo di argomento/parametro al nome usato per il collegamento.

A titolo informativo, è possibile specificare extern "C" a ogni singola dichiarazione/definizione in modo esplicito o utilizzare un blocco per raggruppare una sequenza di dichiarazioni/definizioni che devono avere un determinato collegamento:

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

Se vi interessano i dettagli tecnici, 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 è richiesto di fornire un collegamento "C
  • Una specifica di collegamento deve essere presente solo nell'ambito dello spazio dei nomi
  • Tutti i tipi di funzione, i nomi di funzione e i nomi di variabile hanno un collegamento linguistico Vedere il Commento di Richard: Solo i nomi di funzione e i nomi di variabile con collegamento esterno hanno un collegamento linguistico
  • Due tipi di funzioni con collegamenti linguistici distinti sono tipi distinti anche se altrimenti identici
  • Le specifiche di collegamento si annidano, quella interna determina il collegamento finale
  • extern "C" è ignorato per i membri di una classe
  • Una funzione al massimo con un particolare nome può avere un collegamento "C" (indipendentemente dallo spazio dei nomi)
  • extern "C" costringe una funzione ad avere un collegamento esterno (non può renderla statica) Vedere il commento di Richard: static all'interno extern "C" è valid; un'entità così dichiarata ha un collegamento interno, e quindi non ha un collegamento linguistico
  • Il collegamento dal C++ a oggetti definiti in altri linguaggi e a oggetti definiti in C++ da altri linguaggi è definito dall'implementazione e dipende dal linguaggio. Solo quando le strategie di layout degli oggetti delle implementazioni di due linguaggi sono sufficientemente simili è possibile ottenere tale collegamento.

Volevo solo aggiungere un'informazione, visto che non l'ho ancora vista pubblicata.

Molto spesso si vede del codice nelle intestazioni del C come questo:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Questo permette di usare quel file di intestazione C con il codice C++, perché la macro "__cplusplus" sarà definita. Ma è possibile anche ancora utilizzarlo con il codice C legacy, dove la macro è NON definita, quindi non vedrà il costrutto univoco del C++.

Tuttavia, ho visto anche codice C++ come:

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

che immagino realizzi più o meno la stessa cosa.

Non sono sicuro di quale sia il modo migliore, ma li ho visti entrambi.

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

Compilare e disassemblare l'output ELF generato:

g++ -c -std=c++11 -Wall -Wextra -pedantic -o main.o main.cpp
readelf -s main.o

L'output contiene:

     8: 0000000000000000     7 FUNC    GLOBAL DEFAULT    1 _Z1fv
     9: 0000000000000007     7 FUNC    GLOBAL DEFAULT    1 ef
    10: 000000000000000e    17 FUNC    GLOBAL DEFAULT    1 _Z1hv
    11: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    12: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z1gv
    13: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND eg

Interpretazione

Vediamo che:

  • ef e eg sono stati memorizzati in simboli con lo stesso nome del codice

  • gli altri simboli sono stati maciullati. Vediamo di districarli:

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

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

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

Quindi è necessario extern "C" entrambi al momento della chiamata:

  • C da C ++: dire g++ di aspettarsi i simboli non manipolati prodotti da gcc
  • C++ da C: dire a g++ di generare simboli non modificati per gcc per utilizzare

Cose che non funzionano in extern C

È ovvio che qualsiasi caratteristica del C++ che richieda la manipolazione dei nomi 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  void f(C i) { }
}

Esempio di C minimo eseguibile da C ++

Per completezza e per i neofiti, si veda anche: Come usare i file sorgente C in un progetto C++?

Chiamare il C dal C++ è piuttosto semplice: ogni funzione C ha solo un possibile simbolo non mutato, quindi non è necessario alcun lavoro aggiuntivo.

main.cpp

#include 

#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++ 
 * because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c

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

Esempio su GitHub.

Esempio minimo di C++ eseguibile da C

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

Qui illustriamo come esporre gli overload delle funzioni C++ in C.

main.c

#include 

#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 maciullati che gcc non può trovare.

Esempio su GitHub.

Dove si trova il file extern "c" quando includo le intestazioni C dal C++?

  • Le versioni C++ delle intestazioni C come cstdio potrebbero fare affidamento su #pragma GCC system_header che https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html cita: "Su alcuni target, come RS/6000 AIX, GCC circonda implicitamente tutte le intestazioni di sistema con un blocco 'extern "C"' quando si compila in C++", ma non ho avuto piena conferma.
  • Intestazioni POSIX come /usr/include/unistd.h sono trattati in: È necessario un blocco extern "C" per includere intestazioni POSIX C standard? via __BEGIN_DECLSriprodotto su Ubuntu 20.04. __BEGIN_DECLS è incluso tramite #include .

Testato in Ubuntu 18.04.

Sezione recensioni e valutazioni

Se questo post ti è stato utile, sarebbe molto utile se lo condividessi con più sviluppatori in questo modo ci aiuti a diffondere queste informazioni.



Utilizzate il nostro motore di ricerca

Ricerca
Generic filters

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.