Warum brauchen wir extern “C” {#include } in C ++?

Warum brauchen wir:

extern "C" { #include  } 

Speziell:

  • Wann sollten wir es benutzen?

  • Was passiert auf der Compiler / Linker-Ebene, die es erfordert, dass wir sie verwenden?

  • Wie triggers das in Bezug auf die Kompilierung / Verknüpfung die Probleme, die uns erfordern?

    C und C ++ sind oberflächlich ähnlich, aber jedes kompiliert zu einem ganz anderen Satz von Code. Wenn Sie eine Headerdatei mit einem C ++ – Compiler einbeziehen, erwartet der Compiler C ++ – Code. Wenn es sich jedoch um einen C-Header handelt, erwartet der Compiler, dass die in der Header-Datei enthaltenen Daten in ein bestimmtes Format kompiliert werden – das C ++ – “ABI” oder das “Application Binary Interface”, so dass der Linker aufstockt. Dies ist vorzuziehen, wenn C ++ – Daten an eine function übergeben werden, die C-Daten erwartet.

    (Um wirklich auf den Punkt zu kommen, “zerquetscht” C ++ ‘s ABI im Allgemeinen die Namen ihrer functionen / Methoden, so dass printf() _Zprintf , ohne den Prototyp als C-function zu _Zprintf , erzeugt C ++ tatsächlich den Aufruf von _Zprintf und zusätzlichem Mist Am Ende.)

    Also: benutze extern "C" {...}; wenn ac-header eingeschlossen ist – so einfach ist das. Andernfalls wird der kompilierte Code nicht übereinstimmen und der Linker wird ersticken. Für die meisten Header benötigen Sie jedoch nicht einmal das extern da die meisten System-C-Header bereits dafür verantwortlich sind, dass sie in C ++ – Code enthalten sind und ihren Code bereits extern .

    extern “C” legt fest, wie Symbole in der generierten Objektdatei benannt werden sollen. Wenn eine function ohne extern “C” deklariert wird, wird der Symbolname in der Objektdatei C ++ – Name Mangling verwenden. Hier ist ein Beispiel.

    Gegeben Test.C wie folgt:

     void foo() { } 

    Kompilieren und Auflisten von Symbolen in der Objektdatei gibt:

     $ g++ -c test.C $ nm test.o 0000000000000000 T _Z3foov U __gxx_personality_v0 

    Die foo-function heißt eigentlich “_Z3foov”. Diese Zeichenfolge enthält unter anderem Typinformationen für den Rückgabetyp und die Parameter. Wenn Sie stattdessen test.C so schreiben:

     extern "C" { void foo() { } } 

    Dann kompiliere und betrachte Symbole:

     $ g++ -c test.C $ nm test.o U __gxx_personality_v0 0000000000000000 T foo 

    Sie erhalten C-Verknüpfung. Der Name der “foo” -function in der Objektdatei ist nur “foo”, und sie hat nicht alle Informationen über die Phantasie, die von der Namensänderung kommt.

    Normalerweise fügen Sie einen Header in extern “C” {} ein, wenn der dazugehörige Code mit einem C-Compiler kompiliert wurde, aber Sie versuchen, ihn von C ++ aus aufzurufen. Wenn Sie dies tun, sagen Sie dem Compiler, dass alle Deklarationen in der Kopfzeile die C-Verknüpfung verwenden. Wenn Sie Ihren Code verknüpfen, enthalten Ihre O-Dateien Verweise auf “foo”, nicht “_Z3fooblah”, die hoffentlich mit dem übereinstimmt, was in der Bibliothek vorhanden ist, mit der Sie verlinken.

    Die meisten modernen Bibliotheken werden Wächter um solche Header herum stellen, so dass Symbole mit der richtigen Verknüpfung deklariert werden. zB in vielen der Standard-Header finden Sie:

     #ifdef __cplusplus extern "C" { #endif ... declarations ... #ifdef __cplusplus } #endif 

    Dadurch wird sichergestellt, dass die Symbole in Ihrer Objektdatei mit denen in der C-Bibliothek übereinstimmen, wenn C ++ – Code den Header enthält. Sie sollten nur extern “C” {} um Ihren C-Header legen müssen, wenn es alt ist und diese Wächter noch nicht haben.

    In C ++ können Sie verschiedene Entitäten haben, die einen Namen teilen. Zum Beispiel hier ist eine Liste von functionen alle namens foo :

    • A::foo()
    • B::foo()
    • C::foo(int)
    • C::foo(std::string)

    Um alle zu unterscheiden, erstellt der C ++ – Compiler eindeutige Namen für jeden in einem process namens Name-Mangling oder Dekoration. C-Compiler machen das nicht. Außerdem kann jeder C ++ – Compiler dies auf andere Weise tun.

    extern “C” weist den C ++ – Compiler an, keine Namensänderung am Code innerhalb der geschweiften Klammern vorzunehmen. Dadurch können Sie C-functionen innerhalb von C ++ aufrufen.

    Es hat mit der Art zu tun, wie die verschiedenen Compiler Namens-Mangling durchführen. Ein C ++ – Compiler wird den Namen eines aus der Header-Datei exportierten Symbols auf eine völlig andere Weise als ein C-Compiler verändern. Wenn Sie also versuchen, eine Verknüpfung herzustellen, erhalten Sie einen Linker-Fehler, der besagt, dass Symbole fehlen.

    Um dies zu beheben, weisen wir den C ++ – Compiler an, im “C” -Modus zu laufen, so dass der Name-Mangling genauso ausgeführt wird wie der C-Compiler. Wenn Sie dies getan haben, sind die Linker-Fehler behoben.

    Wann sollten wir es benutzen?

    Wenn Sie C-Bibliotheken in C ++ – Objektdateien verknüpfen

    Was passiert auf der Compiler / Linker-Ebene, die es erfordert, dass wir sie verwenden?

    C und C ++ verwenden unterschiedliche Schemata für die Symbolbenennung. Dies weist den Linker an, das Schema von C beim Verknüpfen in der gegebenen Bibliothek zu verwenden.

    Wie triggers das in Bezug auf die Kompilierung / Verknüpfung die Probleme, die uns erfordern?

    Mithilfe des C-Benennungsschemas können Sie auf Symbole im C-Stil verweisen. Andernfalls würde der Linker C ++ – Style-Symbole ausprobieren, die nicht funktionieren würden.

    C und C ++ haben unterschiedliche Regeln für die Namen von Symbolen. Symbole wissen, wie der Linker weiß, dass der Aufruf der function “openBankAccount” in einer vom Compiler erzeugten Objektdatei ein Verweis auf die function ist, die Sie in einer anderen Objektdatei “openBankAccount” genannt haben, die von einer anderen Quelldatei erzeugt wurde. Compiler. Auf diese Weise können Sie ein Programm aus mehr als einer Quelldatei erstellen, was eine Erleichterung bei der Arbeit an einem großen Projekt darstellt.

    In C ist die Regel sehr einfach, Symbole befinden sich sowieso alle in einem einzigen Namensraum. Also wird die Ganzzahl “socks” als “socks” gespeichert und die function count_socks wird als “count_socks” gespeichert.

    Linker wurden für C und andere Sprachen wie C mit dieser einfachen Symbolbenennungsregel erstellt. Symbole im Linker sind also nur einfache Zeichenfolgen.

    Aber in C ++ erlaubt die Sprache Namespaces, Polymorphismen und verschiedene andere Dinge, die mit solch einer einfachen Regel in Konflikt stehen. Alle sechs Ihrer polymorphen functionen, die “add” genannt werden, müssen unterschiedliche Symbole haben, oder das falsche wird von anderen Objektdateien verwendet. Dies geschieht durch “Mangeln” (das ist ein technischer Begriff) die Namen von Symbolen.

    Wenn Sie C ++ – Code mit C-Bibliotheken oder -Code verknüpfen, benötigen Sie externes “C”, alles in C geschriebene Headerdateien für die C-Bibliotheken, um Ihrem C ++ – Compiler mitzuteilen, dass diese Symbolnamen nicht verändert werden sollen Ihr C ++ Code muss natürlich manipuliert werden oder es wird nicht funktionieren.

    Sie sollten immer extern “C” verwenden, wenn Sie einen Header einfügen, der functionen definiert, die sich in einer Datei befinden, die von einem C-Compiler kompiliert wurde und in einer C ++ – Datei verwendet wird. (Viele Standard-C-Bibliotheken können diese Überprüfung in ihren Kopfzeilen enthalten, um es für den Entwickler einfacher zu machen)

    Wenn Sie beispielsweise ein Projekt mit drei Dateien, util.c, util.h und main.cpp haben und beide Dateien mit dem C ++ – Compiler (g ++, cc, usw.) kompiliert werden, dann ist es nicht t wirklich benötigt, und kann sogar Linker-Fehler verursachen. Wenn Ihr Build-process einen regulären C-Compiler für util.c verwendet, müssen Sie extern “C” verwenden, wenn Sie util.h einschließen.

    Was passiert, ist, dass C ++ die Parameter der function in ihrem Namen codiert. So funktioniert das Überladen von functionen. Alles was mit einer C-function passiert, ist das Hinzufügen eines Unterstrichs (“_”) zum Anfang des Namens. Ohne Verwendung von extern “C” wird der Linker nach einer function namens DoSomething @@ int @ float () suchen, wenn der tatsächliche Name der function _DoSomething () oder nur DoSomething () ist.

    Die Verwendung von extern “C” triggers das obige Problem, indem dem C ++ – Compiler mitgeteilt wird, dass er nach einer function suchen soll, die der C-Namenskonvention statt der C ++ – Konvention folgt.

    Das extern "C" {} Konstrukt extern "C" {} weist den Compiler an, keine Namensänderungen in den geschweiften Klammern auszuführen. Normalerweise “erweitert” der C ++ – Compiler functionsnamen, so dass sie Typinformationen über Argumente und den Rückgabewert codieren. Das nennt man den entstellten Namen . Das extern "C" verhindert das Mangeln.

    Es wird normalerweise verwendet, wenn C ++ – Code eine C-Sprachenbibliothek aufrufen muss. Es kann auch verwendet werden, wenn eine C ++ – function (z. B. von einer DLL) für C-Clients verfügbar gemacht wird.

    Der C ++ – Compiler erstellt Symbolnamen anders als der C-Compiler. Wenn Sie also versuchen, eine function aufzurufen, die sich in einer C-Datei befindet, die als C-Code kompiliert wurde, müssen Sie dem C ++ – Compiler mitteilen, dass die Symbolnamen, die aufgetriggers werden sollen, anders aussehen als standardmäßig; Andernfalls wird der Link-Schritt fehlschlagen.

    Dies wird verwendet, um Probleme mit der Namensänderung zu beheben. extern C bedeutet, dass die functionen in einer “flachen” C-Style-API sind.