it-swarm-eu.dev

Warum brauchen wir externes "C" {#include <foo.h>} in C ++?

Warum müssen wir verwenden:

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

Insbesondere:

  • Wann sollten wir es benutzen?

  • Was passiert auf Compiler-/Linker-Ebene, wenn wir es verwenden müssen?

  • Wie löst dies in Bezug auf das Zusammenstellen/Verknüpfen die Probleme, die uns erfordern, es zu verwenden?

133
Landon

C und C++ sind sich oberflächlich ähnlich, werden jedoch jeweils zu einer ganz anderen Codemenge kompiliert. Wenn Sie eine Headerdatei in einen C++ - Compiler einfügen, erwartet der Compiler C++ - Code. Wenn es sich jedoch um einen C-Header handelt, erwartet der Compiler, dass die in der Header-Datei enthaltenen Daten in ein bestimmtes Format kompiliert werden - das C++ 'ABI' oder 'Application Binary Interface'. Dies ist der Übergabe von C++ - Daten an eine Funktion vorzuziehen, die C-Daten erwartet.

(Um auf das Wesentliche einzugehen: C++ 's ABI' verstümmelt 'im Allgemeinen die Namen ihrer Funktionen/Methoden, ruft also printf() auf, ohne den Prototyp als C-Funktion zu kennzeichnen, und generiert tatsächlich einen Codeaufruf _Zprintf, Plus extra Mist am Ende.)

Also: Verwenden Sie extern "C" {...}, Wenn Sie einen c-Header einfügen - so einfach ist das. Andernfalls kommt es zu einer Nichtübereinstimmung im kompilierten Code, und der Linker wird ersticken. Für die meisten Header benötigen Sie jedoch nicht einmal den extern, da die meisten System-C-Header bereits die Tatsache berücksichtigen, dass sie möglicherweise in C++ - Code und bereits in extern ihrem Code enthalten sind.

121
duane

extern "C" legt fest, wie Symbole in der generierten Objektdatei benannt werden sollen. Wenn eine Funktion ohne externes "C" deklariert wird, verwendet der Symbolname in der Objektdatei die C++ - Namensverwaltung. Hier ist ein Beispiel.

Gegebener Test.C gefällt mir so:

void foo() { }

Das Kompilieren und Auflisten von Symbolen in der Objektdatei ergibt:

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

Die foo-Funktion heißt eigentlich "_Z3foov". Diese Zeichenfolge enthält unter anderem Typinformationen für den Rückgabetyp und die Parameter. Wenn Sie stattdessen test.C wie folgt schreiben:

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

Dann kompiliere und betrachte Symbole:

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

Sie erhalten C-Verknüpfung. Der Name der "foo" -Funktion in der Objektdatei ist nur "foo", und sie enthält nicht alle Informationen zu den ausgefallenen Typen, die aus der Namensverwaltung stammen.

Im Allgemeinen fügen Sie einen Header in extern "C" {} ein, wenn der dazugehörige Code mit einem C-Compiler kompiliert wurde, Sie jedoch versuchen, ihn von C++ aus aufzurufen. Wenn Sie dies tun, teilen Sie dem Compiler mit, dass alle Deklarationen in der Kopfzeile C-Verknüpfung verwenden. Wenn Sie Ihren Code verlinken, enthalten Ihre .o-Dateien Verweise auf "foo" und nicht auf "_Z3fooblah", was hoffentlich mit dem übereinstimmt, was sich in der Bibliothek befindet, gegen die Sie verlinken.

Die meisten modernen Bibliotheken werden solche Überschriften mit einem Schutz versehen, sodass Symbole mit der richtigen Verknüpfung deklariert werden. z.B. In vielen Standardüberschriften finden Sie:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Dadurch wird sichergestellt, dass die Symbole in Ihrer Objektdatei mit den Symbolen in der C-Bibliothek übereinstimmen, wenn der C++ - Code den Header enthält. Sie sollten nur externes "C" {} um Ihren C-Header setzen müssen, wenn er alt ist und diese Wachen noch nicht hat.

108
Todd Gamblin

In C++ können Sie verschiedene Entitäten haben, die einen Namen gemeinsam haben. Zum Beispiel ist hier eine Liste aller Funktionen mit dem Namen foo:

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

Um sie alle voneinander zu unterscheiden, erstellt der C++ - Compiler in einem Prozess, der als Name-Mangling oder Dekorieren bezeichnet wird, eindeutige Namen für jeden. C-Compiler tun dies nicht. Darüber hinaus kann jeder C++ - Compiler dies auf andere Weise tun.

extern "C" weist den C++ - Compiler an, keine Namensänderung für den Code in geschweiften Klammern vorzunehmen. Auf diese Weise können Sie C-Funktionen in C++ aufrufen.

21
Trent

Dies hängt mit der Art und Weise zusammen, wie die verschiedenen Compiler die Namensverfälschung durchführen. Ein C++ - Compiler verändert den Namen eines aus der Header-Datei exportierten Symbols auf völlig andere Weise als ein C-Compiler. Wenn Sie also versuchen, eine Verknüpfung herzustellen, wird ein Linker-Fehler angezeigt, der besagt, dass Symbole fehlen.

Um dieses Problem zu beheben, weisen wir den C++ - Compiler an, im "C" -Modus zu arbeiten, sodass die Namensanalyse auf die gleiche Weise wie beim C-Compiler durchgeführt wird. Danach sind die Linker-Fehler behoben.

14

Wann sollten wir es benutzen?

Wenn Sie C-Bibliotheken in C++ - Objektdateien verknüpfen

Was passiert auf Compiler-/Linker-Ebene, wenn wir es verwenden müssen?

C und C++ verwenden unterschiedliche Schemata für die Symbolbenennung. Dies weist den Linker an, das Schema von C zu verwenden, wenn er in der angegebenen Bibliothek verlinkt.

Wie löst dies in Bezug auf das Zusammenstellen/Verknüpfen die Probleme, die uns erfordern, es zu verwenden?

Mit dem C-Benennungsschema können Sie auf Symbole im C-Stil verweisen. Andernfalls würde der Linker C++ - Symbole ausprobieren, die nicht funktionieren würden.

11
Tony M

C und C++ haben unterschiedliche Regeln für die Bezeichnung von Symbolen. Symbole sind, wie der Linker weiß, dass der Aufruf der Funktion "openBankAccount" in einer vom Compiler erstellten Objektdatei eine Referenz auf die Funktion ist, die Sie "openBankAccount" in einer anderen Objektdatei aufgerufen haben, die von derselben (oder einer kompatiblen) Quelldatei erstellt wurde. Compiler. Auf diese Weise können Sie aus mehr als einer Quelldatei ein Programm erstellen. Dies ist eine Erleichterung, wenn Sie an einem großen Projekt arbeiten.

In C ist die Regel sehr einfach, Symbole stehen ohnehin alle in einem Namensraum. Die Ganzzahl "socks" wird also als "socks" und die Funktion count_socks als "count_socks" gespeichert.

Mit dieser einfachen Symbolbenennungsregel wurden Linker für C und andere Sprachen wie C erstellt. Symbole im Linker sind also nur einfache Zeichenfolgen.

In C++ können Sie jedoch Namespaces, Polymorphismus und verschiedene andere Dinge verwenden, die mit einer so einfachen Regel in Konflikt stehen. Alle sechs polymorphen Funktionen, die als "add" bezeichnet werden, müssen unterschiedliche Symbole haben, oder das falsche wird von anderen Objektdateien verwendet. Dies geschieht durch "Zerfleischen" (das ist ein technischer Begriff) der Namen von Symbolen.

Wenn Sie C++ - Code mit C-Bibliotheken oder Code verknüpfen, müssen Sie externes "C" in C schreiben, z. B. Header-Dateien für die C-Bibliotheken, um Ihrem C++ - Compiler mitzuteilen, dass diese Symbolnamen während des restlichen Vorgangs nicht verändert werden sollen Ihr C++ - Code muss natürlich entstellt sein, sonst funktioniert er nicht.

10
tialaramex

Der C++ - Compiler erstellt Symbolnamen anders als der C-Compiler. Wenn Sie also versuchen, eine Funktion aufzurufen, die sich in einer C-Datei befindet, die als C-Code kompiliert wurde, müssen Sie dem C++ - Compiler mitteilen, dass die Symbolnamen, die aufgelöst werden sollen, anders aussehen als standardmäßig. Andernfalls schlägt der Verknüpfungsschritt fehl.

7
mbyrne215

Sie sollten externes "C" immer dann verwenden, wenn Sie einen Header einfügen, der Funktionen definiert, die sich in einer von einem C-Compiler kompilierten Datei befinden, die in einer C++ - Datei verwendet wird. (Viele Standard-C-Bibliotheken enthalten diese Prüfung möglicherweise in ihren Kopfzeilen, um sie für den Entwickler zu vereinfachen.)

Wenn Sie beispielsweise ein Projekt mit drei Dateien haben, nämlich util.c, util.h und main.cpp, und die Dateien .c und .cpp mit dem C++ - Compiler kompiliert wurden (g ++, cc usw.), ist dies nicht der Fall. Wird nicht wirklich benötigt und kann sogar zu Linkerfehlern führen. Wenn Ihr Erstellungsprozess einen regulären C-Compiler für util.c verwendet, müssen Sie externes "C" verwenden, wenn Sie util.h einschließen.

Was passiert ist, dass C++ die Parameter der Funktion in ihrem Namen kodiert. So funktioniert Funktionsüberladung. Alles, was bei einer C-Funktion passieren kann, ist das Hinzufügen eines Unterstrichs ("_") am Anfang des Namens. Ohne Verwendung von extern "C" sucht der Linker nach einer Funktion mit dem Namen DoSomething @@ int @ float (), wenn der tatsächliche Name der Funktion _DoSomething () oder nur DoSomething () ist.

Die Verwendung von extern "C" löst das obige Problem, indem der C++ - Compiler angewiesen wird, nach einer Funktion zu suchen, die der C-Namenskonvention anstelle der C++ - Funktion folgt.

7
HitScan

Das extern "C" {} construct weist den Compiler an, die in geschweiften Klammern deklarierten Namen nicht zu entstellen. Normalerweise "erweitert" der C++ - Compiler Funktionsnamen, sodass sie Typinformationen über Argumente und den Rückgabewert codieren. Dies nennt man verstümmelter Name. Das extern "C" Konstrukt verhindert das Zerfleischen.

Es wird normalerweise verwendet, wenn C++ - Code eine Bibliothek in C-Sprache aufrufen muss. Es kann auch verwendet werden, wenn eine C++ - Funktion (z. B. von einer DLL) für C-Clients verfügbar gemacht wird.

6
Paul Lalonde

Dies wird verwendet, um Probleme mit der Namensverknüpfung zu beheben. extern C bedeutet, dass sich die Funktionen in einer "flachen" C-artigen API befinden.

5
Eric Z Beard

Dekompilieren Sie eine g++ - generierte Binärdatei, um zu sehen, was los ist

Ich gehe in dieser Antwort von Folgendem aus: Was bewirkt externes "C" in C++? , da diese Frage als Duplikat dieser Frage angesehen wurde.

main.cpp

void f() {}
void g();

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

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

Kompiliere mit GCC 4.8 Linux ELF Ausgabe:

g++ -c main.cpp

Dekompilieren Sie die Symboltabelle:

readelf -s main.o

Die Ausgabe enthält:

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

Interpretation

Wir sehen das:

  • ef und eg wurden in Symbolen mit demselben Namen wie im Code gespeichert

  • die anderen Symbole wurden entstellt. Lassen Sie uns sie entwirren:

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

Fazit: Die beiden folgenden Symboltypen wurden nicht entstellt:

  • definiert
  • deklariert, aber undefiniert (Ndx = UND), zur Link- oder Laufzeit aus einer anderen Objektdatei bereitzustellen

Sie benötigen also extern "C" Beide, wenn Sie anrufen:

  • C aus C++: Sagen Sie g++, Dass Sie nicht verwirrte Symbole erwarten sollen, die von gcc erzeugt werden.
  • C++ von C: Sagen Sie g++, Dass nicht verwickelte Symbole für gcc generiert werden sollen

Dinge, die in externem C nicht funktionieren

Es wird offensichtlich, dass alle C++ - Funktionen, die eine Namensänderung erfordern, in extern C Nicht funktionieren:

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

Minimal lauffähiges C aus C++ Beispiel

Der Vollständigkeit halber und für die Newbs da draußen, siehe auch: Wie verwende ich C-Quelldateien in einem C++ - Projekt?

Das Aufrufen von C aus C++ ist ziemlich einfach: Jede C-Funktion verfügt nur über ein mögliches nicht entstelltes Symbol, sodass keine zusätzlichen Arbeiten erforderlich sind.

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

c.c.

#include "c.h"

int f(void) { return 1; }

Lauf:

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

Ohne extern "C" Schlägt die Verknüpfung fehl mit:

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

weil g++ erwartet, ein entstelltes f zu finden, das gcc nicht erzeugt hat.

Beispiel auf GitHub .

Minimal lauffähiges C++ aus C-Beispiel

Das Aufrufen von C++ ist etwas schwieriger: Wir müssen manuell nicht entstellte Versionen jeder Funktion erstellen, die wir verfügbar machen möchten.

Hier wird gezeigt, wie C++ - Funktionsüberladungen ausgesetzt werden.

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

Lauf:

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

Ohne extern "C" Scheitert es mit:

main.c:6: undefined reference to `f_int'
main.c:7: undefined reference to `f_float'

weil g++ verstümmelte Symbole erzeugt hat, die gcc nicht finden kann.

Beispiel auf GitHub .

Getestet in Ubuntu 18.04.