2166 Stimmen

Was bewirkt extern "C" in C++?

Was bewirkt das Einfügen von extern "C" in C++-Code genau?

Zum Beispiel:

extern "C" {
   void foo();
}

119 Stimmen

Ich möchte Ihnen diesen Artikel vorstellen: http://www.agner.org/optimize/calling_conventions.pdf Er erzählt Ihnen viel mehr über Aufrufkonventionen und die Unterschiede zwischen Compilern.

2023voto

Faisal Vali Punkte 31013

extern "C" bewirkt, dass ein Funktionsname in C++ eine C-Verknüpfung hat (der Compiler verunstaltet den Namen nicht), damit der Client-C-Code Ihre Funktion mit einer C-kompatiblen Headerdatei verknüpfen (verwenden) kann, die nur die Deklaration Ihrer Funktion enthält. Ihre Funktionsdefinition ist in einem Binärformat enthalten (das von Ihrem C++-Compiler kompiliert wurde), das der Client-C-Linker dann unter Verwendung des C-Namens verknüpfen wird.

Da C++ die Überladung von Funktionsnamen hat und C nicht, kann der C++-Compiler den Funktionsnamen nicht einfach als eindeutige ID zum Verknüpfen verwenden. Daher verunstaltet er den Namen, indem er Informationen über die Argumente hinzufügt. Ein C-Compiler muss den Namen nicht verunstalten, da Sie Funktionsnamen in C nicht überladen können. Wenn Sie angeben, dass eine Funktion in C++ eine extern "C"-Verknüpfung hat, fügt der C++-Compiler keine Argument-/Parameterinformationen zum für die Verknüpfung verwendeten Namen hinzu.

Nur so zur Information: Sie können jedem einzelnen Deklaration/Definition explizit eine extern "C"-Verknüpfung angeben oder einen Block verwenden, um eine Sequenz von Deklarationen/Definitionen zu gruppieren, damit sie eine bestimmte Verknüpfung haben:

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

Wenn Sie sich um die technischen Details kümmern, sind sie im Abschnitt 7.5 des C++03-Standards aufgelistet. Hier ist eine kurze Zusammenfassung (mit Schwerpunkt auf extern "C"):

  • extern "C" ist eine Verknüpfungsspezifikation
  • Jeder Compiler ist verpflichtet, "C"-Verknüpfung bereitzustellen
  • Ein Verknüpfungsspezifikator darf nur im Namensbereich auftreten
  • Alle Funktionstypen, Funktionsnamen und Variablennamen haben eine Sprachverknüpfung Siehe Richards Kommentar: Nur Funktionsnamen und Variablennamen mit externer Verknüpfung haben eine Sprachverknüpfung
  • Zwei Funktionstypen mit unterschiedlichen Sprachverknüpfungen sind selbst dann unterschiedliche Typen, wenn sie ansonsten identisch sind
  • Verknüpfungsspezifikationen können verschachtelt werden, die innere bestimmt die endgültige Verknüpfung
  • extern "C" wird für Klassenmember ignoriert
  • Höchstens eine Funktion mit einem bestimmten Namen kann "C"-Verknüpfung haben (unabhängig vom Namensraum)
  • extern "C" zwingt eine Funktion, eine externe Verknüpfung zu haben (kann sie nicht statisch machen) Siehe Richards Kommentar: static innerhalb von extern "C" ist zulässig; eine so deklarierte Entität hat internen Verknüpfung, und hat daher keine Sprachverknüpfung
  • Verknüpfung von C++ zu Objekten, die in anderen Sprachen definiert sind, und zu Objekten, die in C++ aus anderen Sprachen definiert sind, ist implementierungsabhängig und sprachabhängig. Nur wenn die Objektlayoutstrategien von zwei Sprachimplementierungen ähnlich genug sind, kann eine solche Verknüpfung erreicht werden

0 Stimmen

Dies scheint unter der Motorhaube zu sein ... gibt es einen funktionalen Grund, warum ich 'extern "C"' vor eine Funktion setzen würde (außer sie von einem separaten C-Programm aus aufzurufen)?

40 Stimmen

Der C-Compiler verwendet keine Verstümmelung, wie es der von C++ tut. Daher, wenn Sie eine C-Schnittstelle aus einem C++-Programm aufrufen möchten, müssen Sie diese C-Schnittstelle deutlich als "extern c" deklarieren.

2 Stimmen

@Litherum - Wenn Sie nicht beabsichtigen, dass Ihr Code von einem anderen C++-Compiler oder einem C-Compiler oder einer dynamischen Sprache (Ruby, Python usw.), für die Sie eine Erweiterung entwickeln, verknüpft wird, benötigen Sie kein `extern "C"`. Es bringt Ihnen nichts.

449voto

UncaAlby Punkte 4659

Einfach nur eine kurze Information hinzufügen, da ich das noch nicht gepostet gesehen habe.

Sie werden sehr oft Code in C-Headern sehen wie folgt:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Was damit erreicht wird, ist, dass es Ihnen erlaubt, diese C-Header-Datei mit Ihrem C++-Code zu verwenden, weil das Makro __cplusplus definiert wird. Aber Sie können es auch immer noch mit Ihrem Legacy-C-Code verwenden, wo das Makro NICHT definiert ist, sodass es das spezifisch C++-Konstrukt nicht sehen wird.

Obwohl ich auch schon C++-Code gesehen habe wie:

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

was wahrscheinlich ähnliches erreicht.

Bin mir nicht sicher, welcher Weg besser ist, aber ich habe schon beides gesehen.

13 Stimmen

Es gibt einen deutlichen Unterschied. Im Falle des Ersteren wird, wenn Sie diese Datei mit einem normalen gcc-Compiler kompilieren, ein Objekt erzeugt, in dem der Funktionsname nicht verunstaltet ist. Wenn Sie dann C- und C++-Objekte mit dem Linker verknüpfen, werden die Funktionen NICHT gefunden. Sie müssen diese "Legacy-Header"-Dateien mit dem Schlüsselwort extern wie in Ihrem zweiten Codeblock einbinden.

13 Stimmen

@Anne: Der C++-Compiler sucht auch nach nicht veränderten Namen, weil er extern "C" im Header gesehen hat). Es funktioniert großartig, ich habe diese Technik schon viele Male verwendet.

1 Stimmen

@Ben. Ich habe mich auf den Satz Nicht sicher, welche Methode besser ist, aber ich habe beide gesehen. bezogen. Es gibt einen Unterschied! Wenn Sie die erste Datei mit gcc kompilieren, trifft es nicht auf den Ausdruck extern "C"... Wenn Sie also den zweiten Ausdruck nicht in Ihrem C++-Code verwenden, haben Sie immer noch Probleme. Es gibt keinen Unterschied, wenn Sie denselben Compiler für alle Dateien verwenden.

432voto

Dekompilieren eines mit g++ generierten Binärdateis, um zu sehen, was passiert

main.cpp

void f() {}
void g();

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

/* Verhindern, dass g und eg optimiert werden. */
void h() { g(); eg(); }

Kompilieren und Disassemblieren der generierten ELF-Ausgabe:

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

Die Ausgabe enthält:

     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

Interpretation

Wir sehen, dass:

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

  • die anderen Symbole verändert wurden. Lassen Sie uns sie entziffern:

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

Schlussfolgerung: Beide der folgenden Symboltypen wurden nicht verändert:

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

Daher müssen Sie extern "C" verwenden, sowohl beim Aufrufen von:

  • C von C++: signalisieren Sie g++, dass unveränderte Symbole erwartet werden, die von gcc erzeugt wurden
  • C++ von C: signalisieren Sie g++, dass unveränderte Symbole generiert werden sollen, die von gcc verwendet werden sollen

Dinge, die in extern C nicht funktionieren

Es wird offensichtlich, dass alle C++-Funktionen, die Namensveränderungen erfordern, innerhalb von extern C nicht funktionieren:

extern "C" {
    // Überladung.
    // 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) { }
}

Minimal ausführliches C von C++ Beispiel

Zum Zwecke der Vollständigkeit und für die Anfänger da draußen, siehe auch: Wie verwendet man C-Quelldateien in einem C++-Projekt?

C von C++ aufrufen ist ziemlich einfach: Jede C-Funktion hat nur ein mögliches nicht verändertes Symbol, daher ist keine zusätzliche Arbeit erforderlich.

main.cpp

#include 

#include "c.h"

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

c.h

#ifndef C_H
#define C_H

/* Dieses ifdef ermöglicht die Verwendung des Headers sowohl von C als auch von C++ 
 * weil C nicht weiß, was dieses extern "C" ist. */
#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" schlägt der Link fehl mit:

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

weil g++ erwartet, ein verändertes f zu finden, das von gcc nicht produziert wurde.

Beispiel auf GitHub.

Minimal ausführliches C++ von C Beispiel

C++ von C aufrufen ist etwas schwieriger: Wir müssen manuell nicht veränderte Versionen jeder Funktion erstellen, die wir freilegen möchten.

Hier zeigen wir, wie C++-Funktionsüberladungen an C freigegeben werden können.

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 kann diese überladenen Prototypen nicht sehen, da es sonst verwirrt wird.
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" schlägt es fehl mit:

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

weil g++ veränderte Symbole erzeugt hat, die gcc nicht finden kann.

Beispiel auf GitHub.

Wo ist das extern "c", wenn ich C-Header-Dateien von C++ einbinde?

Getestet in Ubuntu 18.04.

54 Stimmen

Beste Antwort, da du 1) explizit erwähnst, dass extern "C" { dir hilft, nicht mangled C-Funktionen innerhalb von C++-Programmen aufzurufen, sowie nicht mangled C++-Funktionen innerhalb von C-Programmen, was in anderen Antworten nicht so offensichtlich ist, und 2) weil du deutliche Beispiele dafür zeigst. Danke!

1 Stimmen

Ich frage mich über C-Header wie unistd.h, sys/stat.h und sys/types.h. Es scheint, dass sie das "'C'" nicht nach dem "extern" setzen. Die Verwendung von ihnen aus C++-Code scheint immer noch unproblematisch zu sein. Ist der Grund, dass dies reine Header ohne Implementierungsdatei sind?

1 Stimmen

@Paul sie scheinen externes C mit dem Makro __BEGIN_DECLS zu aktivieren: stackoverflow.com/questions/8087438/… Ich beobachte, was in dieser Antwort auf Ubuntu 20.04 für unistd.h erwähnt wird. Für cstdio dürfte es jedoch auf #pragma GCC system_header beruhen: gcc.gnu.org/onlinedocs/cpp/System-Headers.html

231voto

sud03r Punkte 18137

In jedem C++-Programm werden alle nicht-statischen Funktionen in der Binärdatei als Symbole dargestellt. Diese Symbole sind spezielle Textzeichenketten, die eine Funktion im Programm eindeutig identifizieren.

In C ist der Symbolname derselbe wie der Funktionsname. Dies ist möglich, weil in C keine zwei nicht-statischen Funktionen denselben Namen haben können.

Weil C++ Überladen zulässt und viele Funktionen hat, die C nicht hat - wie Klassen, Elementfunktionen, Ausnahmebedingungen -, ist es nicht möglich, den Funktionsnamen einfach als Symbolnamen zu verwenden. Um dieses Problem zu lösen, verwendet C++ sogenanntes Name Mangling, das den Funktionsnamen und alle erforderlichen Informationen (wie die Anzahl und Größe der Argumente) in eine seltsam aussehende Zeichenkette umwandelt, die nur vom Compiler und Linker verarbeitet wird.

Wenn Sie also eine Funktion als extern C deklarieren, führt der Compiler kein Name Mangling damit durch und es kann direkt über ihren Symbolnamen als Funktionsname zugegriffen werden.

Dies ist nützlich, wenn Sie dlsym() und dlopen() verwenden, um solche Funktionen aufzurufen.

0 Stimmen

Was meinen Sie mit praktisch? Bedeutet Symbolname = Funktionsname, dass der Symbolname, der an dlsym übergeben wird, bekannt ist, oder etwas anderes?

3 Stimmen

@Fehler: Ja. Im Allgemeinen ist es praktisch unmöglich, eine C++-Shared-Library mit nur einer Header-Datei mittels dlopen() zu laden und die richtige Funktion auszuwählen. (Auf x86 gibt es eine veröffentlichte Namensmangel-Spezifikation in Form des Itanium ABI, die alle x86-Compiler, von denen ich weiß, verwenden, um C++-Funktionsnamen zu mangeln. Aber nichts in der Sprache verlangt dies.)

99voto

tim-montague Punkte 13518

C++ verschnörkelt Funktionsnamen, um aus einer prozeduralen Sprache eine objektorientierte Sprache zu erstellen

Die meisten Programmiersprachen werden nicht auf bereits bestehenden Programmiersprachen aufgebaut. C++ basiert auf C und ist außerdem eine objektorientierte Programmiersprache, die aus einer prozeduralen Programmiersprache entwickelt wurde. Aus diesem Grund gibt es in C++ Ausdrücke wie extern "C", die eine Abwärtskompatibilität mit C ermöglichen.

Werfen wir einen Blick auf folgendes Beispiel:

#include <stdio.h>

// Zwei Funktionen sind mit dem gleichen Namen definiert
//   haben jedoch unterschiedliche Parameter

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

Ein C-Compiler wird das obige Beispiel nicht kompilieren, da die Funktion printMe zweimal definiert ist (auch wenn sie unterschiedliche Parameter int a vs char a haben).

gcc -o printMe printMe.c && ./printMe;
1 Fehler. PrintMe ist mehr als einmal definiert.

Ein C++-Compiler wird das obige Beispiel jedoch kompilieren. Es ist nicht darauf bedacht, dass printMe zweimal definiert ist.

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

Dies liegt daran, dass ein C++-Compiler Funktionen implizit umbenennt (verschnörkelt), basierend auf ihren Parametern. Die Sprache wurde entworfen, um objektorientiert zu sein - um verschiedene Klassen mit Methoden (Funktionen) des gleichen Namens zu erstellen und Methodennamen zu überschreiben (Methodenüberschreibung) basierend auf unterschiedlichen Parametern.

Was extern "C" sagt, ist "C-Funktionsnamen nicht verschnörkeln"

Auch wenn C++ auf C basiert, kann das Verschnörkeln für C-Code problematisch sein. Stellen Sie sich zum Beispiel vor, wir haben eine Legacy-C-Datei namens "parent.c", die Funktionsnamen aus verschiedenen Headerdateien wie "parent.h", "child.h", etc. einbindet. Wenn wir "parent.c" durch einen C++-Compiler laufen lassen, werden die Funktionsnamen in dieser Datei verschlüsselt und entsprechen nicht mehr den in den Headerdateien angegebenen Funktionsnamen. Daher müssten die Funktionsnamen in den Headerdateien "parent.h" und "child.h" ebenfalls verschlüsselt werden. Dies mag für einige Dateien in Ordnung sein, aber wenn das C-Programm komplex ist, kann das Verschnörkeln langsam sein und zu fehlerhaftem Code führen. Daher kann es praktisch sein, ein Schlüsselwort anzugeben, das dem C++-Compiler mitteilt, keine Funktionsnamen zu verschlüsseln.

Das Schlüsselwort extern "C" sagt einem C++-Compiler, keine C-Funktionsnamen zu verschlüsseln (umbenennen).

Zum Beispiel:

extern "C" void printMe(int a);

1 Stimmen

Können wir extern "C" nicht verwenden, wenn wir nur eine dll-Datei haben? Ich meine, wenn wir keine Header-Datei haben und nur eine Quelldatei haben (nur Implementierungen) und die Verwendung seiner Funktion über Funktionsszeiger. In diesem Zustand haben wir nur Funktionen verwendet (unabhängig von ihrem Namen).

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X