Frage

Warum brauchen wir verwenden:

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

Im Einzelnen:

  • Wann sollten wir es verwenden?

  • Was an der Compiler / Linker Ebene geschieht, die uns, es zu benutzen erfordert?

  • Wie in Bezug auf die Zusammenstellung / Verknüpfung bedeutet dies die Probleme lösen, die es erforderlich machen, es zu benutzen?

War es hilfreich?

Lösung

C und C ++ sind oberflächlich ähnlich, aber jeder kompiliert in einen ganz anderen Satz von Code. Wenn Sie eine Header-Datei mit einem Compiler C ++ enthalten, erwartet der Compiler C ++ Code. Wenn es jedoch ein C-Header, dann erwartet der Compiler der in der Header-Datei enthaltenen Daten zu einem bestimmten Format-C kompiliert werden ++ ‚ABI‘ oder ‚Application Binary Interface‘, so die Linker Drosseln auf. Dies ist vorzuziehen vorbei C ++ Daten zu einer Funktion C-Daten erwartet.

(Um in das wirklich ans Eingemachtes, C ++ 's ABI im Allgemeinen ‚Mangeln‘ die Namen ihrer Funktionen / Methoden, so printf() Aufruf ohne den Prototyp als C-Funktion markiert wird, wird die C ++ tatsächlich generiert Code _Zprintf Aufruf, plus extra Mist am Ende.)

So: Verwendung extern "C" {...} wenn ein c einschließlich Kopf es ist so einfach. Andernfalls werden Sie eine Diskrepanz in kompilierten Code haben, und der Linker ersticken. Für die meisten Header, aber Sie werden nicht einmal die extern benötigen, weil die meisten System C-Header wird bereits die Tatsache erklären, dass sie von C ++ Code enthalten sein können und extern bereits ihren Code.

Andere Tipps

extern „C“ bestimmt, wie Symbole in der erzeugten Objektdatei benannt werden sollen. Wenn eine Funktion ohne extern „C“ deklariert wird, wird der Symbolname in der Objektdatei C ++ Namen Mangeln verwenden. Hier ist ein Beispiel.

Da test.c etwa so:

void foo() { }

Kompilieren und Auflistung Symbole in der Objektdatei gibt:

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

Die foo Funktion ist eigentlich "_Z3foov" genannt. Diese Zeichenfolge enthält Informationen für den Rückgabetyp und die Parameter eingeben, unter anderem. Wenn Sie stattdessen schreiben test.c wie folgt aus:

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

Dann kompilieren und buchen, Symbole:

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

Sie erhalten C-Bindung. Der Name der „foo“ -Funktion in der Objektdatei ist nur „foo“, und es hat nicht alle Phantasie Art Informationen, die vom Namen Mangeln kommt.

Sie enthalten in der Regel einen Header innerhalb extern „C“ {}, wenn der Code, der mit ihm geht mit einem C-Compiler kompiliert wurde, aber Sie versuchen, es von C ++ zu nennen. Wenn Sie das tun, doch sagen Sie den Compiler, dass alle Erklärungen im Header C-Bindung verwendet werden. Wenn Sie Ihren Code verknüpfen, werden Ihre .o Dateien Verweise auf „foo“, nicht „_Z3fooblah“, die hoffentlich übereinstimmt, was auch immer in der Bibliothek ist gegen Sie verbinden.

Die meisten modernen Bibliotheken Wachen um solche Header setzen, so dass die Symbole mit der richtigen Verknüpfung deklariert sind. z.B. in vielen der Standard-Header finden Sie:

#ifdef __cplusplus
extern "C" {
#endif

... declarations ...

#ifdef __cplusplus
}
#endif

Dies stellt sicher, dass, wenn C ++ Code enthält den Header, die Symbole in Ihrer Objektdatei übereinstimmen, was in der C-Bibliothek ist. Sie sollten nur extern „C“ {} um die C-Header setzen müssen, wenn sie alt ist und diese Wachen nicht bereits hat.

In C ++ können Sie verschiedene Einheiten haben, die einen Namen haben. Zum Beispiel ist hier eine Liste von Funktionen alle Namen foo :

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

Um zwischen allen, die C ++ Compiler erstellen eindeutige Namen für jede in einem Prozess namens name-Mangeln oder Dekoration zu unterscheiden. C-Compiler tun dies nicht. Weiterhin ist jeder C ++ Compiler dies tun kann, ist eine andere Art und Weise.

extern „C“ erzählt die C ++ Compiler keinen Namen-Mangeln auszuführen innerhalb der geschweiften Klammern auf dem Code. Auf diese Weise können Sie C-Funktionen aus C ++ aufrufen.

Es hat mit der Art und Weise der verschiedenen Compiler ausführen Name-Mangeln zu tun. Ein C ++ Compiler wird den Namen eines Symbols aus der Header-Datei in eine völlig andere Art und Weise als ein C-Compiler exportiert zerfleischen würde, also wenn Sie zu verknüpfen versuchen, würden Sie ein Linker-Fehler gab es fehlende Symbole erhalten zu sagen.

Um dies zu beheben, sagen wir dem C ++ Compiler in „C“ Modus ausgeführt werden, so führt er Namen auf die gleiche Weise Mangeln des C-Compiler würde. so werden die Linker-Fehler behoben getan zu haben.

  

Wann sollten wir es verwenden?

Wenn Sie eine Verknüpfung C libaries in C ++ Objektdateien

  

Was bei der geschieht   Compiler / Linker Ebene, die uns verlangt   um es zu verwenden?

C und C ++ verwenden unterschiedliche Systeme für die Symbol Namensgebung. Dies teilt den Linker C Schema zu verwenden, wenn in der gegebenen Bibliothek verknüpft.

  

Wie in Bezug auf die Erstellung / Verknüpfung   bedeutet dies die Probleme lösen, die   verlangt von uns, es zu benutzen?

, um das C-Namensschema ermöglicht es Ihnen, C-Stil Symbole zu verweisen. Sonst wäre der Linker versuchen C ++ -. Stil Symbole, die nicht funktionieren würde

C und C ++ über Namen von Symbolen unterschiedliche Regeln. Die Symbole sind wie der Linker weiß, dass der Ruf „openBankAccount“ in einer Objektdatei durch den Compiler zu dieser Funktion ist eine Referenz erzeugt funktionieren Sie „openBankAccount“ in einer anderen Objektdatei aus einer anderen Quelldatei mit dem gleichen (oder kompatibel) hergestellt genannt Compiler. Auf diese Weise können Sie aus mehr als einer Quelldatei, ein Programm zu machen, das eine Erleichterung ist, wenn an einem großen Projekt zu arbeiten.

In C ist die Regel sehr einfach, Symbole sowieso alle in einem einzigen Namensraum sind. Also die ganze Zahl „Socken“ als „Socken“ gespeichert und die Funktion count_socks wird als „count_socks“ gespeichert.

Linkers wurde für C und andere Sprachen wie C mit dieser einfachen Symbol Benennungsregel gebaut. So Symbole in den Linker sind nur einfache Strings.

Aber in C ++ die Sprache können Sie Namespaces und Polymorphismus und verschiedene andere Dinge, die im Widerspruch mit einer solchen einfachen Regel. Alle sechs Ihre polymorphen Funktionen namens „add“ muß verschiedene Symbole haben, oder die falschen wird von anderen Objektdateien verwendet werden. Dies wird durch „Mangeln“ getan (das ist ein technischer Begriff) die Namen der Symbole.

Bei der Verknüpfung C ++ Code zu C-Bibliotheken oder Code, müssen Sie extern „C“ alles in C geschrieben, wie die Header-Dateien für die C-Bibliotheken, zu sagen, Ihrem C ++ Compiler, diese Symbolnamen verstümmelt werden sollen, nicht, während der Rest des C ++ Code natürlich verstümmelt sein muss oder es wird nicht funktionieren.

Sie sollten extern „C“ jederzeit verwenden, die Sie einen Header definieren Funktionen enthalten in einer Datei durch einen C-Compiler kompiliert mit Wohnsitz in einer C ++ Datei verwendet. (Viele Standard-C-Bibliotheken können diese Überprüfung in ihrem Header enthalten, um es einfacher für den Entwickler)

Zum Beispiel, wenn Sie ein Projekt mit drei Dateien, util.c, util.h und main.cpp und sowohl die .c und CPP-Dateien werden mit der C ++ Compiler (g ++, cc, etc.) zusammengestellt dann es ist nicht wirklich nötig, und auch Linkerfehler verursachen kann. Wenn Ihr Build-Prozess einen regelmäßigen C-Compiler für util.c verwendet, dann müssen Sie extern „C“ verwenden, wenn einschließlich util.h.

Was passiert ist, dass C ++ die Parameter der Funktion in ihrem Namen codiert. Dies ist, wie Funktion Überlastung funktioniert. Alles, was zu einer C-Funktion geschehen neigt ist die Zugabe von einem Unterstrich ( „_“) an den Anfang des Namens. Ohne Verwendung extern "C" wird der Linker für eine Funktion suchen namens DoSomething @@ int @ float (), wenn der tatsächliche Name der Funktion wird _DoSomething () oder nur DoSomething ().

Mit extern „C“ löst das obige Problem durch den C ++ Compiler zu sagen, dass es für eine Funktion aussehen soll, die die C-Namenskonvention anstelle der C ++ man folgt.

Die C ++ Compiler erstellt Symbolnamen anders als die C-Compiler. Also, wenn Sie einen Anruf an eine Funktion zu machen versuchen, die in einer C-Datei befindet, als C-Code kompiliert, müssen Sie den C ++ Compiler sagen, dass die Symbolnamen, die es anders zu lösen versucht aussehen, als es standardmäßig; andernfalls der Link Schritt wird fehlschlagen.

Das extern "C" {} Konstrukt weist den Compiler nicht innerhalb der geschweiften Klammern deklariert Namen Mangeln auszuführen. Normalerweise „verbessert“ die C ++ Compiler Funktionsnamen, so dass sie Typinformationen über Argumente und den Rückgabewert codieren; dies ist der verstümmelten Name genannt. Das extern "C" Konstrukt verhindert das Mangeln.

Es wird in der Regel verwendet, wenn C ++ Code, um eine C-Sprache Bibliothek aufrufen muss. Es kann auch verwendet werden, wenn eine C ++ Funktion (aus einer DLL, zum Beispiel) zu C Kunden ausgesetzt wird.

Dies wird verwendet, Namen Mangeln Probleme zu lösen. extern C bedeutet, dass die Funktionen in einem "flachen" C-style API sind.

Decompile ein g++ erzeugten binären zu sehen, was los ist,

ich in dieser Antwort bewegte aus: Was ist der Effekt von extern „C“ in C ++? da diese Frage ist ein Duplikat dieser ein betrachtet 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(); }

Kompilieren mit GCC 4.8 Linux ELF Ausgabe:

g++ -c main.cpp

Decompile der 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, dass:

  • ef und eg wurden in Symbolen mit dem gleichen Namen wie in dem Code

  • gespeichert
  • die anderen Symbole wurden verstümmelt. Lassen Sie uns unmangle sie:

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

Fazit: die beiden folgenden Symboltypen waren nicht verstümmelten:

  • definiert
  • deklariert, aber nicht definiert (Ndx = UND), an der Verbindung oder zur Laufzeit von einem anderen Objekt-Datei zur Verfügung gestellt werden

Sie werden also extern "C" müssen sowohl beim Aufruf:

  • C von C ++: tell g++ unmangled Symbole von gcc produziert erwarten
  • C ++ von C: tell g++ unmangled Symbole zu erzeugen, für gcc zu verwenden

Dinge, die in extern C nicht funktionieren

Es wird offensichtlich, dass jede C ++ Funktion, die Namen Mangeln erfordert innerhalb extern C wird 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 runnable C von C ++ Beispiel

Aus Gründen der Vollständigkeit und die newbs gibt, siehe auch: Wie C-Quelldateien in einem C ++ Projekt verwenden?

Der Aufruf C von C ++ ist recht einfach:. Jede C-Funktion hat nur eine mögliche Nicht-zerfleischt Symbol, so dass keine zusätzliche Arbeit erforderlich ist,

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

C.C

#include "c.h"

int f(void) { return 1; }

Ausführen:

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" die Verbindung fehlschlägt mit:

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

da g++ erwartet einen verstümmelten f zu finden, die gcc nicht produzieren.

Beispiel auf GitHub .

Minimal runnable C ++ von C Beispiel

C ++ Aufruf von etwas härter. Wir haben manuell nicht-verstümmelte Versionen jeder Funktion, die wir machen möchten erstellen

Hier zeigen wir, wie C ++ belichten Funktion Überlastungen 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);
}

Ausführen:

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" es nicht mit:

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

da g++ verstümmelten Symbole erzeugt, die gcc nicht finden kann.

Beispiel auf GitHub .

Getestet in Ubuntu 18.04.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top