Was bewirkt das Einfügen von extern "C"
in C++-Code genau?
Zum Beispiel:
extern "C" {
void foo();
}
Was bewirkt das Einfügen von extern "C"
in C++-Code genau?
Zum Beispiel:
extern "C" {
void foo();
}
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üpfungsspezifikationextern "C"
wird für Klassenmember ignoriertextern "C"
zwingt eine Funktion, eine externe Verknüpfung zu haben (kann sie nicht statisch machen)static
innerhalb von extern "C"
ist zulässig; eine so deklarierte Entität hat internen Verknüpfung, und hat daher keine Sprachverknüpfung
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)?
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.
@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.
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.
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.
@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.
@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.
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:
Ndx = UND
), um zur Link- oder Laufzeit von einer anderen Objektdatei bereitzustellenDaher müssen Sie extern "C"
verwenden, sowohl beim Aufrufen von:
g++
, dass unveränderte Symbole erwartet werden, die von gcc
erzeugt wurdeng++
, dass unveränderte Symbole generiert werden sollen, die von gcc
verwendet werden sollenDinge, 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.
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.
Wo ist das extern "c"
, wenn ich C-Header-Dateien von C++ einbinde?
cstdio
stützen sich möglicherweise auf #pragma GCC system_header
, das auf https://gcc.gnu.org/onlinedocs/cpp/System-Headers.html erwähnt wird: "Für einige Ziele, wie RS/6000 AIX, umgibt GCC implizit alle Systemheader mit einem 'extern "C"'-Block beim Kompilieren als C++.", aber ich habe es nicht vollständig bestätigt./usr/include/unistd.h
sind unter Do I need an extern "C" block to include standard POSIX C headers? über __BEGIN_DECLS
abgedeckt, reproduziert auf Ubuntu 20.04. __BEGIN_DECLS
wird über #include
eingeschlossen.Getestet in Ubuntu 18.04.
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!
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?
@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
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.
Was meinen Sie mit praktisch? Bedeutet Symbolname = Funktionsname, dass der Symbolname, der an dlsym übergeben wird, bekannt ist, oder etwas anderes?
@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.)
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.
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);
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 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.
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.