Was genau macht das Einfügen extern "C"
in C ++ - Code?
Zum Beispiel:
extern "C" {
void foo();
}
foo()
Funktion haben.
Was genau macht das Einfügen extern "C"
in C ++ - Code?
Zum Beispiel:
extern "C" {
void foo();
}
foo()
Funktion haben.
Antworten:
extern "C" bewirkt, dass ein Funktionsname in C ++ eine 'C'-Verknüpfung hat (der Compiler entstellt den Namen nicht), sodass der Client-C-Code mithilfe einer' C'-kompatiblen Header-Datei, die nur die enthält, eine Verknüpfung zu Ihrer Funktion herstellen (dh diese verwenden) kann Erklärung Ihrer Funktion. Ihre Funktionsdefinition ist in einem Binärformat enthalten (das von Ihrem C ++ - Compiler kompiliert wurde), das der Client-Linker "C" dann mit dem Namen "C" verknüpft.
Da in C ++ Funktionsnamen überladen sind und in C nicht, kann der C ++ - Compiler den Funktionsnamen nicht einfach als eindeutige ID zum Verknüpfen verwenden, sodass der Name durch Hinzufügen von Informationen zu den Argumenten entstellt wird. Der AC-Compiler muss den Namen nicht entstellen, da Sie Funktionsnamen in C nicht überladen können. Wenn Sie angeben, dass eine Funktion in C ++ über eine externe "C" -Verbindung verfügt, fügt der C ++ - Compiler dem verwendeten Namen keine Informationen zu Argumenten / Parametertypen hinzu Verknüpfung.
Damit Sie wissen, können Sie die Verknüpfung "C" zu jeder einzelnen Deklaration / Definition explizit angeben oder einen Block verwenden, um eine Folge von Deklarationen / Definitionen zu gruppieren, um eine bestimmte Verknüpfung zu erhalten:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
Wenn Sie sich für die technischen Details interessieren, sind diese in Abschnitt 7.5 des C ++ 03-Standards aufgeführt. Hier eine kurze Zusammenfassung (mit Schwerpunkt auf externem "C"):
extern "C" { int i; }
eine Definition ist. Dies ist möglicherweise nicht das, was Sie beabsichtigt haben, neben der Nichtdefinition von void g(char);
. Um es zu einer Nichtdefinition zu machen, müssten Sie extern "C" { extern int i; }
. Andererseits macht die Syntax mit einer Deklaration ohne geschweifte Klammern die Deklaration zu einer extern "C" int i;
extern "C" { extern int i; }
Ich wollte nur ein paar Informationen hinzufügen, da ich sie noch nicht 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
Damit können Sie diese C-Header-Datei mit Ihrem C ++ - Code verwenden, da das Makro "__cplusplus" definiert wird. Sie können es aber auch weiterhin mit Ihrem alten C-Code verwenden, in dem das Makro NICHT definiert ist, sodass das eindeutige C ++ - Konstrukt nicht angezeigt wird.
Obwohl ich auch C ++ - Code gesehen habe wie:
extern "C" {
#include "legacy_C_header.h"
}
was ich mir vorstelle, bewirkt fast dasselbe.
Ich bin mir nicht sicher, welcher Weg besser ist, aber ich habe beide gesehen.
extern "C"
im Header angezeigt wird . Es funktioniert super, hat diese Technik oft benutzt.
extern "C"
vor oder nach dem Einfügen des Headers auftritt. Bis es den Compiler erreicht, ist es sowieso nur ein langer Strom von vorverarbeitetem Text.
g++
hat dies in den letzten 17 Jahren für irgendein Ziel zu irgendeinem Zeitpunkt falsch verstanden. Der springende Punkt des ersten Beispiels ist, dass es keine Rolle spielt, ob Sie einen C- oder C ++ - Compiler verwenden. Für die Namen im extern "C"
Block wird keine Namensverknüpfung durchgeführt .
Dekompilieren Sie eine g++
generierte Binärdatei, um zu sehen, was los ist
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 und disassemblieren Sie die generierte 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
Deutung
Wir sehen das:
ef
und eg
wurden in Symbolen mit dem gleichen Namen wie im Code gespeichert
Die anderen Symbole wurden verstümmelt. Lassen Sie uns sie entwirren:
$ c++filt _Z1fv
f()
$ c++filt _Z1hv
h()
$ c++filt _Z1gv
g()
Schlussfolgerung: Beide der folgenden Symboltypen wurden nicht entstellt:
Ndx = UND
), zur Verknüpfung oder Laufzeit aus einer anderen Objektdatei bereitzustellenSie benötigen also extern "C"
beide, wenn Sie anrufen:
g++
, dass Sie entwirrte Symbole erwarten sollen, die von erzeugt werdengcc
g++
Weisen Sie an, nicht verschränkte Symbole für gcc
die Verwendung zu generierenDinge, die in extern C nicht funktionieren
Es wird offensichtlich, dass jede C ++ - Funktion, die eine Namensveränderung erfordert, im Inneren nicht funktioniert extern C
:
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 ausführbares C aus C ++ - Beispiel
Der Vollständigkeit halber und für die Neulinge 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 verstümmeltes Symbol, sodass keine zusätzliche Arbeit erforderlich ist.
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++
* because C does not know what this extern "C" thing is. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif
#endif
cc
#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"
den Link schlägt fehl mit:
main.cpp:6: undefined reference to `f()'
denn g++
erwartet, eine verstümmelte zu finden f
, die gcc
nicht produziert hat.
Minimal ausführbares C ++ aus C-Beispiel
Das Aufrufen von C ++ von C aus ist etwas schwieriger: Wir müssen nicht entstellte Versionen jeder Funktion, die wir verfügbar machen möchten, manuell erstellen.
Hier zeigen wir, wie C ++ - Funktionsüberladungen C 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++
erzeugte verstümmelte Symbole, die gcc
nicht finden können.
Getestet in Ubuntu 18.04.
extern "C" {
Sie nicht entwirrte C-Funktionen aus C ++ - Programmen heraus aufrufen können , sowie entwirrte C ++ - Funktionen aus C ++ - Programmen heraus , die andere Antworten nicht so offensichtlich machen, und 2) weil Sie unterschiedliche Beispiele dafür zeigen jeder. Vielen Dank!
In jedem C ++ - Programm werden alle nicht statischen Funktionen in der Binärdatei als Symbole dargestellt. Diese Symbole sind spezielle Textzeichenfolgen, die eine Funktion im Programm eindeutig identifizieren.
In C entspricht der Symbolname dem Funktionsnamen. Dies ist möglich, weil in C keine zwei nicht statischen Funktionen denselben Namen haben können.
Da C ++ eine Überladung zulässt und viele Funktionen aufweist, die C nicht bietet - wie Klassen, Elementfunktionen, Ausnahmespezifikationen -, kann der Funktionsname nicht einfach als Symbolname verwendet werden. Um dies zu lösen, verwendet C ++ das sogenannte Name Mangling, das den Funktionsnamen und alle erforderlichen Informationen (wie die Anzahl und Größe der Argumente) in eine seltsam aussehende Zeichenfolge umwandelt, die nur vom Compiler und Linker verarbeitet wird.
Wenn Sie also eine Funktion als extern C angeben, führt der Compiler keine Namensverwaltung durch und kann direkt auf sie zugreifen, indem sein Symbolname als Funktionsname verwendet wird.
Dies ist praktisch, wenn Sie solche Funktionen verwenden dlsym()
und dlopen()
aufrufen.
Die meisten Programmiersprachen bauen nicht auf vorhandenen Programmiersprachen auf. C ++ baut auf C auf und ist darüber hinaus eine objektorientierte Programmiersprache, die aus einer prozeduralen Programmiersprache aufgebaut ist. Aus diesem Grund gibt es C ++ - Ausdrücke, extern "C"
die Abwärtskompatibilität mit C bieten.
Schauen wir uns das folgende Beispiel an:
#include <stdio.h>
// Two functions are defined with the same name
// but have different parameters
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;
}
Der AC-Compiler kompiliert das obige Beispiel nicht, da dieselbe Funktion printMe
zweimal definiert wird (obwohl sie unterschiedliche Parameter int a
vs haben char a
).
gcc -o printMe printMe.c && ./printMe;
1 Fehler. PrintMe wird mehrmals definiert.
Ein C ++ - Compiler kompiliert das obige Beispiel. Es ist egal, dass printMe
zweimal definiert wird.
g ++ -o printMe printMe.c && ./printMe;
Dies liegt daran, dass ein C ++ - Compiler Funktionen implizit basierend auf ihren Parametern umbenennt ( mangelt ). In C wurde diese Funktion nicht unterstützt. Wenn C ++ jedoch über C erstellt wurde, war die Sprache objektorientiert konzipiert und musste die Fähigkeit unterstützen, verschiedene Klassen mit gleichnamigen Methoden (Funktionen) zu erstellen und Methoden ( Methodenüberschreibung ) basierend auf verschiedenen Methoden zu überschreiben Parameter.
extern "C"
sagt "C-Funktionsnamen nicht entstellen"Stellen Sie sich jedoch vor, wir haben eine Legacy-C-Datei mit dem Namen "parent.c", deren include
Funktionsnamen aus anderen Legacy-C-Dateien "parent.h", "child.h" usw. stammen. Wenn die Legacy-Datei "parent.c" ausgeführt wird Durch einen C ++ - Compiler werden die Funktionsnamen entstellt und stimmen nicht mehr mit den in "parent.h", "child.h" usw. angegebenen Funktionsnamen überein. Daher müssten auch die Funktionsnamen in diesen externen Dateien verwendet werden verstümmelt werden. Das Mangeln von Funktionsnamen in einem komplexen C-Programm mit vielen Abhängigkeiten kann zu fehlerhaftem Code führen. Daher kann es zweckmäßig sein, ein Schlüsselwort anzugeben, das den C ++ - Compiler anweist, einen Funktionsnamen nicht zu entstellen.
Das extern "C"
Schlüsselwort weist einen C ++ - Compiler an, C-Funktionsnamen nicht zu entstellen (umzubenennen).
Zum Beispiel:
extern "C" void printMe(int a);
extern "C"
wenn wir nur eine dll
Datei haben? Ich meine, wenn wir keine Header-Datei haben und nur eine Quelldatei (nur Implementierungen) und die Verwendung ihrer Funktion über den Funktionszeiger. In diesem Zustand haben wir nur Funktionen verwendet (unabhängig von ihrem Namen).
Kein C-Header kann mit C ++ kompatibel gemacht werden, indem lediglich externes "C" eingeschlossen wird. Wenn Bezeichner in einem C-Header mit C ++ - Schlüsselwörtern in Konflikt stehen, beschwert sich der C ++ - Compiler darüber.
Zum Beispiel habe ich gesehen, dass der folgende Code in einem g ++ fehlschlägt:
extern "C" {
struct method {
int virtual;
};
}
Ein bisschen macht Sinn, ist aber etwas zu beachten, wenn Sie C-Code nach C ++ portieren.
extern "C"
bedeutet, die C-Verknüpfung zu verwenden, wie in anderen Antworten beschrieben. Es bedeutet nicht, "den Inhalt als C zu kompilieren" oder so. int virtual;
ist in C ++ ungültig und die Angabe einer anderen Verknüpfung ändert daran nichts.
Es ändert die Verknüpfung einer Funktion so, dass die Funktion von C aus aufgerufen werden kann. In der Praxis bedeutet dies, dass der Funktionsname nicht entstellt wird .
undname
.
Es weist den C ++ - Compiler an, beim Verknüpfen die Namen dieser Funktionen in einem C-Stil nachzuschlagen, da die Namen der in C und C ++ kompilierten Funktionen während der Verknüpfungsphase unterschiedlich sind.
Ich habe 'extern "C"' zuvor für DLL-Dateien (Dynamic Link Library) verwendet, um usw. die main () -Funktion "exportierbar" zu machen, damit sie später in einer anderen ausführbaren Datei von DLL verwendet werden kann. Vielleicht kann ein Beispiel dafür nützlich sein, wo ich es früher verwendet habe.
DLL
#include <string.h>
#include <windows.h>
using namespace std;
#define DLL extern "C" __declspec(dllexport)
//I defined DLL for dllexport function
DLL main ()
{
MessageBox(NULL,"Hi from DLL","DLL",MB_OK);
}
EXE
#include <string.h>
#include <windows.h>
using namespace std;
typedef LPVOID (WINAPI*Function)();//make a placeholder for function from dll
Function mainDLLFunc;//make a variable for function placeholder
int main()
{
char winDir[MAX_PATH];//will hold path of above dll
GetCurrentDirectory(sizeof(winDir),winDir);//dll is in same dir as exe
strcat(winDir,"\\exmple.dll");//concentrate dll name with path
HINSTANCE DLL = LoadLibrary(winDir);//load example dll
if(DLL==NULL)
{
FreeLibrary((HMODULE)DLL);//if load fails exit
return 0;
}
mainDLLFunc=(Function)GetProcAddress((HMODULE)DLL, "main");
//defined variable is used to assign a function from dll
//GetProcAddress is used to locate function with pre defined extern name "DLL"
//and matcing function name
if(mainDLLFunc==NULL)
{
FreeLibrary((HMODULE)DLL);//if it fails exit
return 0;
}
mainDLLFunc();//run exported function
FreeLibrary((HMODULE)DLL);
}
extern "C"
und __declspec(dllexport)
sind nicht verwandt. Ersteres steuert die Symboldekoration, letzteres ist für die Erstellung eines Exporteintrags verantwortlich. Sie können ein Symbol auch mit C ++ - Namensdekoration exportieren. Abgesehen davon, dass der Punkt dieser Frage völlig verfehlt wurde, gibt es auch andere Fehler im Codebeispiel. Zum einen main
deklariert der Export aus Ihrer DLL keinen Rückgabewert. Oder Konvention nennen. Beim Importieren weisen Sie eine zufällige Aufrufkonvention zu ( WINAPI
) und verwenden das falsche Symbol für 32-Bit-Builds (sollte _main
oder sein _main@0
). Entschuldigung, -1.
void*
, aber Ihre Implementierung gibt nichts zurück. Das wird wirklich gut fliegen ...
extern "C"
ist eine Verknüpfungsspezifikation, mit der C-Funktionen in den Cpp-Quelldateien aufgerufen werden . Wir können C-Funktionen aufrufen, Variablen schreiben und Header einschließen . Die Funktion wird in einer externen Entität deklariert und außerhalb definiert. Syntax ist
Typ 1:
extern "language" function-prototype
Typ 2:
extern "language"
{
function-prototype
};
z.B:
#include<iostream>
using namespace std;
extern "C"
{
#include<stdio.h> // Include C Header
int n; // Declare a Variable
void func(int,int); // Declare a function (function prototype)
}
int main()
{
func(int a, int b); // Calling function . . .
return 0;
}
// Function definition . . .
void func(int m, int n)
{
//
//
}
Diese Antwort ist für die ungeduldigen / zu befolgenden Fristen, nur ein Teil / eine einfache Erklärung ist unten:
Also
in C ++, mit dem Namen Mangeln eindeutig Identitäten jede Funktion
in C, auch ohne Namen Mangeln eindeutig Identitäten jede Funktion
Um das Verhalten von C zu ändern ++, das heißt, zu spezifizieren , dass Namen Mangeln nicht sollte für eine bestimmte Funktion geschehen, können Sie extern „C“ vor dem Funktionsnamen, für welche Gründe auch immer, wie eine Funktion mit einem bestimmten Namen aus einer DLL exportieren , zur Verwendung durch seine Kunden.
Lesen Sie andere Antworten, um detailliertere / korrektere Antworten zu erhalten.
Beim Mischen von C und C ++ (dh a. Aufrufen der C-Funktion von C ++ und b. Aufrufen der C ++ - Funktion von C) verursacht das Mangeln von C ++ - Namen Verknüpfungsprobleme. Technisch gesehen tritt dieses Problem nur auf, wenn die Angerufenenfunktionen bereits mit dem entsprechenden Compiler in Binärdateien (höchstwahrscheinlich eine * .a-Bibliotheksdatei) kompiliert wurden.
Wir müssen also externes "C" verwenden, um das Mangeln von Namen in C ++ zu deaktivieren.
Ohne mit anderen guten Antworten in Konflikt zu geraten, werde ich ein wenig von meinem Beispiel hinzufügen.
Was genau der C ++ - Compiler tut: Er entstellt die Namen im Kompilierungsprozess, daher müssen wir den Compiler anweisen, die C
Implementierung speziell zu behandeln .
Wenn wir C ++ - Klassen erstellen und hinzufügen extern "C"
, teilen wir unserem C ++ - Compiler mit, dass wir die C-Aufrufkonvention verwenden.
Grund (wir rufen die C-Implementierung aus C ++ auf): Entweder möchten wir die C-Funktion aus C ++ aufrufen oder die C ++ - Funktion aus C aufrufen (C ++ - Klassen ... usw. funktionieren in C nicht).
Eine von einem C-Compiler kompilierte Funktion void f () und eine von einem C ++ - Compiler kompilierte Funktion void f () sind nicht dieselbe Funktion. Wenn Sie diese Funktion in C geschrieben und dann versucht haben, sie von C ++ aus aufzurufen, sucht der Linker nach der C ++ - Funktion und findet die C-Funktion nicht.
extern "C" teilt dem C ++ - Compiler mit, dass Sie eine Funktion haben, die vom C-Compiler kompiliert wurde. Sobald Sie ihm mitteilen, dass es vom C-Compiler kompiliert wurde, weiß der C ++ - Compiler, wie er es korrekt aufruft.
Außerdem kann der C ++ - Compiler eine C ++ - Funktion so kompilieren, dass der C-Compiler sie aufrufen kann. Diese Funktion wäre offiziell eine C-Funktion, aber da sie vom C ++ - Compiler kompiliert wird, kann sie alle C ++ - Funktionen verwenden und verfügt über alle C ++ - Schlüsselwörter.
extern "C"
Funktion kompilieren - und (unter bestimmten Einschränkungen) kann sie durch Code aufgerufen werden, der von einem C-Compiler kompiliert wurde.