Warum brauchen wir extern „C“ {#include } in C ++?
-
09-06-2019 - |
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?
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
undeg
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 vongcc
produziert erwarten - C ++ von C: tell
g++
unmangled Symbole zu erzeugen, fürgcc
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.
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.
Getestet in Ubuntu 18.04.