it-swarm.com.de

Was bewirkt externes "C" in C ++?

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

Zum Beispiel:

extern "C" {
   void foo();
}
1452
Litherum

extern "C" bewirkt, dass ein Funktionsname in C++ eine "C" -Verknüpfung aufweist (der Name wird vom Compiler nicht verändert), 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), mit dem der Client-C-Linker dann unter Verwendung des C-Namens verknüpft wird.

Da in C++ die Funktionsnamen überladen sind und in C nicht, kann der C++ - Compiler den Funktionsnamen nicht einfach als eindeutige ID für die Verknüpfung verwenden. Daher wird der Name durch Hinzufügen von Informationen zu den Argumenten entstellt. 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++ eine externe "C" -Verknüpfung hat, fügt der C++ - Compiler dem für verwendeten Namen keine Argument-/Parametertypinformationen hinzu Verknüpfung.

Damit Sie wissen, können Sie die Verknüpfung "C" zu jeder einzelnen Deklaration/Definition explizit angeben oder mithilfe eines Blocks eine Folge von Deklarationen/Definitionen 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, finden Sie diese in Abschnitt 7.5 des C++ 03-Standards. Hier ist eine kurze Zusammenfassung (mit Schwerpunkt auf externem "C"):

  • extern "C" ist eine Verknüpfungsspezifikation
  • Jeder Compiler muss eine "C" -Verknüpfung bereitstellen
  • eine Verknüpfungsspezifikation darf nur im Namespace-Bereich auftreten
  • alle Funktionstypen, Funktions- und Variablennamen sind sprachlich verknüpftSiehe Richards Kommentar: Nur Funktionsnamen und Variablennamen mit externer Verknüpfung haben eine Sprachverknüpfung
  • zwei Funktionstypen mit unterschiedlichen Sprachverknüpfungen sind unterschiedliche Typen, auch wenn sie ansonsten identisch sind
  • verknüpfungsspezifikationen nisten, innere bestimmt die endgültige Verknüpfung
  • extern "C" wird für Klassenmitglieder ignoriert
  • höchstens eine Funktion mit einem bestimmten Namen kann eine "C" -Verknüpfung haben (unabhängig vom Namespace)
  • extern "C" erzwingt eine externe Verknüpfung einer Funktion (kann sie nicht statisch machen)Siehe Richards Kommentar: 'static' inside 'extern "C" ist gültig; Eine so deklarierte Entität verfügt über eine interne Verknüpfung und somit über keine Sprachverknüpfung
  • Die Verknüpfung von C++ mit Objekten, die in anderen Sprachen definiert sind, und Objekten, die in C++ aus anderen Sprachen definiert sind, ist implementierungsdefiniert und sprachabhängig. Nur wenn die Objektlayoutstrategien von zwei Sprachimplementierungen ähnlich genug sind, kann eine solche Verknüpfung erreicht werden
1419
Faisal Vali

Ich wollte nur ein paar Infos hinzufügen, da ich sie noch nicht gepostet habe.

In C-Headern wird häufig Code wie folgt angezeigt:

#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif

Dadurch können Sie diese C-Header-Datei mit Ihrem C++ - Code verwenden, da das Makro "__cplusplus" definiert wird. Sie können es jedoch 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 zum Beispiel:

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

was ich mir vorstelle, erreicht so ziemlich dasselbe.

Ich bin mir nicht sicher, welcher Weg besser ist, aber ich habe beides gesehen.

289
UncaAlby

Dekompiliere 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(); }

Kompiliere und disassembliere 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

Interpretation

Wir sehen das:

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

  • die anderen Symbole wurden entstellt. Lassen Sie uns sie entwirren:

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

Fazit: Die beiden folgenden Symboltypen wurden nicht entstellt:

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

Sie benötigen also extern "C" beide, wenn Sie anrufen:

  • C aus C++: Sagen Sie g++, dass Sie unverwechselbare Symbole erwarten sollen, die von gcc erzeugt wurden.
  • C++ von C: Sagen Sie g++, um nicht verwickelte Symbole für gcc zu generieren

Dinge, die in externem C nicht funktionieren

Es wird offensichtlich, dass alle C++ - Funktionen, die eine Namensverknüpfung erfordern, in extern C 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 lauffähiges C aus C++ Beispiel

Der Vollständigkeit halber und für die Newbs 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++. */
#ifdef __cplusplus
extern "C" {
#endif
int f();
#ifdef __cplusplus
}
#endif

#endif

c.c.

#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" schlägt die Verknüpfung fehl mit:

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

weil g++ erwartet, ein verstümmeltes f zu finden, das gcc nicht produziert hat.

Beispiel auf GitHub .

Minimal lauffähiges C++ aus C-Beispiel

Das Aufrufen von C++ aus C heraus ist etwas schwieriger: Wir müssen manuell nicht entstellte Versionen jeder Funktion erstellen, die wir verfügbar machen möchten.

Hier wird gezeigt, wie C++ - Funktionsüberladungen 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" schlägt fehl mit:

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

weil g++ verstümmelte Symbole erzeugt hat, die gcc nicht finden können.

Beispiel auf GitHub .

Getestet in Ubuntu 18.04.

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 ist der Symbolname derselbe wie der Funktionsname. Dies ist möglich, weil in C keine zwei nicht statischen Funktionen denselben Namen haben können.

Da C++ Überladungen zulässt und viele Funktionen aufweist, die C nicht bietet - wie Klassen, Memberfunktionen und Ausnahmespezifikationen - ist es nicht möglich, den Funktionsnamen einfach als Symbolnamen zu verwenden. Um dies zu lösen, verwendet C++ das sogenannte Name Mangling, bei dem der Funktionsname und alle erforderlichen Informationen (wie die Anzahl und Größe der Argumente) in seltsam aussehende Zeichenfolgen umgewandelt werden, die nur vom Compiler und Linker verarbeitet werden.

Wenn Sie also eine Funktion als externes C angeben, führt der Compiler keine Namensmanipulation durch, und es kann direkt unter Verwendung seines Symbolnamens als Funktionsname darauf zugegriffen werden.

Dies ist praktisch, wenn Sie dlsym() und dlopen() zum Aufrufen solcher Funktionen verwenden.

198
sud03r

C++ definiert Funktionsnamen, um aus einer prozeduralen Sprache eine objektorientierte Sprache zu erstellen

Die meisten Programmiersprachen bauen nicht auf vorhandenen Programmiersprachen auf. C++ baut auf C auf und ist außerdem eine objektorientierte Programmiersprache, die aus einer prozeduralen Programmiersprache aufgebaut ist. Aus diesem Grund gibt es C++ - Ausdrücke wie 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;
}

Ein C-Compiler kompiliert das obige Beispiel nicht, da dieselbe Funktion printMe zweimal definiert wird (obwohl 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 kompiliert das obige Beispiel. Es ist egal, dass printMe zweimal definiert ist.

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

Dies liegt daran, dass ein C++ - Compiler ( mangles ) Funktionen implizit anhand ihrer Parameter umbenennt. In C wurde diese Funktion nicht unterstützt. Als C++ jedoch über C erstellt wurde, war die Sprache objektorientiert und musste die Fähigkeit unterstützen, verschiedene Klassen mit gleichnamigen Methoden (Funktionen) zu erstellen und Methoden ( Überschreiben von Methoden) zu überschreiben ) basierend auf verschiedenen Parametern.

extern "C" sagt "C-Funktionsnamen nicht verfälschen"

Stellen Sie sich jedoch vor, wir haben eine Legacy-C-Datei mit dem Namen "parent.c", die includes Funktionsnamen aus anderen Legacy-C-Dateien, "parent.h", "child.h" usw. enthält msgstr "" "Datei wird durch einen C++ - Compiler ausgeführt, dann werden die Funktionsnamen entstellt und stimmen nicht mehr mit den in" parent.h "," child.h "usw. angegebenen Funktionsnamen überein - also den Funktionsnamen in diesen externen Dateien müsste auch entstellt werden. Das Verwerfen von Funktionsnamen in einem komplexen C-Programm, das viele Abhängigkeiten aufweist, kann zu fehlerhaftem Code führen. Daher kann es nützlich sein, ein Schlüsselwort anzugeben, mit dem der C++ - Compiler angewiesen wird, einen Funktionsnamen nicht zu verfälschen.

Das Schlüsselwort extern "C" weist einen C++ - Compiler an, C-Funktionsnamen nicht zu entstellen (umzubenennen). Anwendungsbeispiel: extern "C" void printMe(int a);

40
tfmontague

Die Verknüpfung einer Funktion wird so geändert, dass die Funktion von C aus aufgerufen werden kann. In der Praxis bedeutet dies, dass der Funktionsname nicht verstümmelt lautet.

27

Nicht jeder 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.

Ich habe zum Beispiel 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.

25
Sander Mertens

Sie 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.

19
Mark Rushakoff

extern "C" soll von einem C++ - Compiler erkannt werden und dem Compiler mitteilen, dass die notierte Funktion im C-Stil kompiliert ist (oder kompiliert werden soll). Während des Verlinkens wird also auf die korrekte Funktionsversion von C verwiesen.

12
Flami

Ich habe zuvor 'extern "C" für DLL (Dynamic Link Library) -Dateien verwendet, um die main () -Funktion usw. "exportierbar" zu machen, damit sie später in einer anderen ausführbaren Datei aus der DLL verwendet werden kann. Vielleicht kann ein Beispiel dafür hilfreich 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);
}
6
SturmCoder

extern "C" ist eine Verknüpfungsspezifikation, die zum Aufrufen von C-Funktionen in den Cpp-Quelldateien verwendet wird. Wir können C-Funktionen aufrufen, Variablen schreiben und Header einfügen. Die Funktion wird in der Entität extern deklariert und außerhalb definiert. Syntax ist

Typ 1:

extern "language" function-prototype

Typ 2:

extern "language"
{
     function-prototype
};

zB:

#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)
{
    //
    //
}
5
Yogeesh H T

Diese Antwort ist für die Ungeduldigen/Fristen einzuhalten, nur ein Teil/eine einfache Erklärung ist unten:

  • in C++ können Sie denselben Namen in der Klasse überladen (zum Beispiel, da sie alle denselben Namen haben und nicht wie sie sind aus der DLL usw. exportiert werden können). Die Lösung für diese Probleme ist, dass sie in verschiedene Zeichenfolgen (Symbole genannt) konvertiert werden ), Symbole berücksichtigt den Namen der Funktion, auch die Argumente, so dass jede dieser Funktionen auch mit dem gleichen Namen eindeutig identifiziert werden kann (auch Name Mangling genannt)
  • in C gibt es keine Überladung, der Funktionsname ist eindeutig (daher ist keine separate Zeichenfolge zur eindeutigen Identifizierung eines Funktionsnamens erforderlich, sodass das Symbol der Funktionsname selbst ist).

Damit
in C++, wobei der Name jede Funktion eindeutig identifiziert
in C, auch ohne Namensverfälschung, identifiziert jede Funktion eindeutig

Um das Verhalten von C++ zu ändern, dh um anzugeben, dass die Namensverknüpfung sollte nicht für eine bestimmte Funktion erfolgen soll, können Sie extern "C" vor dem Funktionsnamen verwenden, z Aus welchem ​​Grund auch immer, zum Beispiel, um eine Funktion mit einem bestimmten Namen aus einer DLL zu exportieren, die von ihren Clients verwendet wird.

Lesen Sie andere Antworten, um detailliertere/korrektere Antworten zu erhalten.

Beim Mischen von C und C++ (d. H. Aufrufen einer C-Funktion von C++ aus und Aufrufen einer C++ - Funktion von C aus) verursacht die C++ - Namensverknüpfung Verknüpfungsprobleme. Technisch gesehen tritt dieses Problem nur dann auf, wenn die aufgerufenen Funktionen bereits mit dem entsprechenden Compiler in eine Binärdatei (höchstwahrscheinlich eine * .a-Bibliotheksdatei) kompiliert wurden.

Wir müssen also externes "C" verwenden, um die Namensverwaltung in C++ zu deaktivieren.

1
Trombe