Ich hatte kürzlich ein Interview und eine Frage war, was die Verwendung extern "C"
in C ++ - Code ist. Ich antwortete, dass es C-Funktionen in C ++ - Code verwenden soll, da C keine Namensverknüpfung verwendet. Ich wurde gefragt, warum C keine Namensverknüpfung verwendet, und um ehrlich zu sein, konnte ich nicht antworten.
Ich verstehe, dass der C ++ - Compiler beim Kompilieren von Funktionen der Funktion einen speziellen Namen gibt, hauptsächlich weil wir in C ++ überladene Funktionen mit demselben Namen haben können, die zum Zeitpunkt der Kompilierung aufgelöst werden müssen. In C bleibt der Name der Funktion gleich oder möglicherweise mit einem _ davor.
Meine Frage lautet: Was ist falsch daran, dem C ++ - Compiler zu erlauben, auch C-Funktionen zu entstellen? Ich hätte angenommen, dass es egal ist, welche Namen der Compiler ihnen gibt. Wir rufen Funktionen in C und C ++ auf die gleiche Weise auf.
quelle
extern "C"
sagt, den Namen genauso zu entstellen, wie es "der" C-Compiler tun würde.Antworten:
Es wurde oben irgendwie beantwortet, aber ich werde versuchen, die Dinge in einen Zusammenhang zu bringen.
Zuerst kam C zuerst. Als solches ist C eine Art "Standard". Namen werden nicht entstellt, weil dies einfach nicht der Fall ist. Ein Funktionsname ist ein Funktionsname. Ein Global ist ein Global und so weiter.
Dann kam C ++. C ++ wollte in der Lage sein, den gleichen Linker wie C zu verwenden und mit in C geschriebenem Code zu verknüpfen. C ++ konnte das C jedoch nicht so belassen (oder fehlen), wie es ist. Schauen Sie sich das folgende Beispiel an:
In C ++ sind dies unterschiedliche Funktionen mit unterschiedlichen Körpern. Wenn keiner von ihnen entstellt ist, werden beide als "Funktion" (oder "_Funktion") bezeichnet, und der Linker beschwert sich über die Neudefinition eines Symbols. Die C ++ - Lösung bestand darin, die Argumenttypen in den Funktionsnamen zu zerlegen. Also wird einer aufgerufen
_function_int
und der andere wird aufgerufen_function_void
(kein tatsächliches Mangling-Schema) und die Kollision wird vermieden.Jetzt haben wir ein Problem. Wenn dies
int function(int a)
in einem C-Modul definiert wurde und wir lediglich dessen Header (dh Deklaration) in C ++ - Code verwenden, wird der Compiler eine Anweisung an den Linker zum Importieren generieren_function_int
. Als die Funktion im C-Modul definiert wurde, wurde sie nicht so genannt. Es wurde gerufen_function
. Dies führt zu einem Linkerfehler.Um diesen Fehler zu vermeiden, teilen wir dem Compiler während der Deklaration der Funktion mit, dass es sich um eine Funktion handelt, die mit einem C-Compiler verknüpft oder von diesem kompiliert werden soll:
Der C ++ - Compiler kann jetzt
_function
eher importieren als_function_int
und alles ist gut.quelle
-std=c++11
und die Verwendung von Dingen außerhalb des Standards vermeiden. Dies entspricht dem Deklarieren einer Java-Version (obwohl neuere Java-Versionen abwärtskompatibel sind). Es ist nicht der Standardfehler, den Compiler-spezifische Erweiterungen und plattformabhängigen Code verwenden. Auf der anderen Seite können Sie ihnen keine Vorwürfe machen, da im Standard viele Dinge (insbesondere E / A, wie Steckdosen) fehlen. Das Komitee scheint das langsam aufzuholen. Korrigieren Sie mich, wenn ich etwas verpasst habe.Es ist nicht so, dass sie "nicht können", sie sind es im Allgemeinen nicht.
Wenn Sie eine Funktion in einer aufgerufenen C-Bibliothek aufrufen möchten
foo(int x, const char *y)
, ist es nicht gut, wenn Ihr C ++ - Compiler dies infoo_I_cCP()
ein Mangle-Schema zerlegt (oder was auch immer, hier wurde nur ein Mangling-Schema erstellt), nur weil dies möglich ist.Dieser Name wird nicht aufgelöst, die Funktion befindet sich in C und ihr Name hängt nicht von der Liste der Argumenttypen ab. Der C ++ - Compiler muss dies also wissen und diese Funktion als C markieren, um das Mangeln zu vermeiden.
Denken Sie daran, dass sich die C-Funktion möglicherweise in einer Bibliothek befindet, deren Quellcode Sie nicht haben. Sie haben lediglich die vorkompilierte Binärdatei und den Header. Ihr C ++ - Compiler kann also nicht "es ist etwas Eigenes", er kann doch nicht ändern, was sich in der Bibliothek befindet.
quelle
extern "C"
:)Sie wären keine C-Funktionen mehr.
Eine Funktion ist nicht nur eine Signatur und eine Definition. Wie eine Funktion funktioniert, wird weitgehend von Faktoren wie der Aufrufkonvention bestimmt. Die für die Verwendung auf Ihrer Plattform angegebene "Application Binary Interface" beschreibt, wie Systeme miteinander kommunizieren. Das von Ihrem System verwendete C ++ - ABI gibt ein Namensverwaltungsschema an, damit Programme auf diesem System wissen, wie Funktionen in Bibliotheken usw. aufgerufen werden. (Lesen Sie das C ++ Itanium ABI für ein gutes Beispiel. Sie werden sehr schnell erkennen, warum es notwendig ist.)
Gleiches gilt für das C ABI auf Ihrem System. Einige C-ABIs verfügen tatsächlich über ein Namensverwaltungsschema (z. B. Visual Studio). Daher geht es hier weniger darum, die Namensverwaltung zu deaktivieren, als vielmehr darum, für bestimmte Funktionen von C ++ ABI zu C ABI zu wechseln. Wir markieren C-Funktionen als C-Funktionen, für die der C ABI (und nicht der C ++ ABI) relevant ist. Die Deklaration muss mit der Definition übereinstimmen (sei es im selben Projekt oder in einer Bibliothek eines Drittanbieters), andernfalls ist die Deklaration sinnlos. Ohne das weiß Ihr System einfach nicht, wie diese Funktionen zu finden / aufzurufen sind.
Warum Plattformen C- und C ++ - ABIs nicht als gleich definieren und dieses "Problem" beseitigen, ist teilweise historisch - die ursprünglichen C-ABIs reichten für C ++ nicht aus, da Namespaces, Klassen und Operatorüberladungen vorhanden sind von denen irgendwie auf computerfreundliche Weise im Namen eines Symbols dargestellt werden müssen - aber man könnte auch argumentieren, dass es für die C-Community unfair ist, C-Programme jetzt an C ++ zu halten, was sich mit einer massiv komplizierteren Situation abfinden müsste ABI nur für einige andere Leute, die Interoperabilität wollen.
quelle
+int(PI/3)
, aber mit einem Körnchen Salz: Ich würde sehr vorsichtig von "C ++ ABI" sprechen ... AFAIK, es gibt Versuche , C ++ ABIs zu definieren, aber keine wirklichen De-facto / De-Jure- Standards - wie isocpp.org/files /papers/n4028.pdf besagt (und ich stimme voll und ganz zu), zitieren, es ist zutiefst ironisch, dass C ++ tatsächlich immer eine Möglichkeit unterstützt hat, eine API mit einem stabilen binären ABI zu veröffentlichen - indem über externes „C“ auf die C-Teilmenge von C ++ zurückgegriffen wird ”. .C++ Itanium ABI
ist genau das - etwas C ++ ABI für Itanium ... wie auf stackoverflow.com/questions/7492180/c-abi-issues-listMSVC in der Tat tut mangle C Namen, wenn auch in einer einfachen Art und Weise. Es hängt manchmal an
@4
oder eine andere kleine Zahl. Dies bezieht sich auf Aufrufkonventionen und die Notwendigkeit einer Stapelbereinigung.Die Prämisse ist also nur fehlerhaft.
quelle
_
?/Gd, /Gr, /Gv, /Gz
. (Das heißt, die Standardaufrufkonvention wird verwendet, es sei denn, eine Funktionsdeklaration gibt explizit eine Aufrufkonvention an.) Sie denken darüber nach,__cdecl
welche Standardkonvention für Anrufe gilt.Es ist sehr üblich, Programme zu haben, die teilweise in C und teilweise in einer anderen Sprache geschrieben sind (oft Assemblersprache, manchmal aber auch Pascal, FORTRAN oder etwas anderes). Es ist auch üblich, dass Programme verschiedene Komponenten enthalten, die von verschiedenen Personen geschrieben wurden, die möglicherweise nicht für alles den Quellcode haben.
Auf den meisten Plattformen gibt es eine Spezifikation, die häufig als ABI (Application Binary Interface) bezeichnet wird und beschreibt, was ein Compiler tun muss, um eine Funktion mit einem bestimmten Namen zu erstellen, die Argumente bestimmter Typen akzeptiert und einen Wert eines bestimmten Typs zurückgibt. In einigen Fällen kann ein ABI mehr als eine "Aufrufkonvention" definieren. Compiler für solche Systeme bieten häufig eine Möglichkeit, anzugeben, welche Aufrufkonvention für eine bestimmte Funktion verwendet werden soll. Auf dem Macintosh verwenden die meisten Toolbox-Routinen beispielsweise die Pascal-Aufrufkonvention. Der Prototyp für "LineTo" lautet also wie folgt:
Wenn der gesamte Code in einem Projekt mit demselben Compiler kompiliert wurde, spielt es keine Rolle, welchen Namen der Compiler für jede Funktion exportiert hat. In vielen Situationen muss C-Code jedoch Funktionen aufrufen, die mit anderen Tools und kompiliert wurden kann mit dem vorliegenden Compiler nicht neu kompiliert werden [und befindet sich möglicherweise nicht einmal in C]. Die Möglichkeit, den Linkernamen zu definieren, ist daher für die Verwendung solcher Funktionen von entscheidender Bedeutung.
quelle
Ich werde eine weitere Antwort hinzufügen, um einige der tangentialen Diskussionen anzusprechen, die stattgefunden haben.
Das C ABI (Application Binary Interface) forderte ursprünglich die Übergabe von Argumenten auf dem Stapel in umgekehrter Reihenfolge (dh von rechts nach links verschoben), wobei der Aufrufer auch den Stapelspeicher freigibt. Das moderne ABI verwendet tatsächlich Register zum Übergeben von Argumenten, aber viele der Überlegungen zum Mangeln gehen auf das Übergeben des ursprünglichen Stapelarguments zurück.
Im Gegensatz dazu schob der ursprüngliche Pascal ABI die Argumente von links nach rechts, und der Angerufene musste die Argumente platzen lassen. Der ursprüngliche C ABI ist dem ursprünglichen Pascal ABI in zwei wichtigen Punkten überlegen. Die Argument-Push-Reihenfolge bedeutet, dass der Stapelversatz des ersten Arguments immer bekannt ist. Dies ermöglicht Funktionen mit einer unbekannten Anzahl von Argumenten, wobei die frühen Argumente steuern, wie viele andere Argumente es gibt (ala
printf
).Die zweite Art und Weise, in der der C ABI überlegen ist, ist das Verhalten, falls sich Anrufer und Angerufene nicht darüber einig sind, wie viele Argumente es gibt. Im Fall C passiert nichts Schlimmes, solange Sie nicht auf Argumente zugreifen, die über das letzte hinausgehen. In Pascal wird die falsche Anzahl von Argumenten aus dem Stapel entfernt und der gesamte Stapel ist beschädigt.
Das ursprüngliche Windows 3.1 ABI basierte auf Pascal. Als solches wurde der Pascal ABI verwendet (Argumente in der Reihenfolge von links nach rechts, Callee Pops). Da eine Nichtübereinstimmung der Argumentnummer zu einer Stapelbeschädigung führen kann, wurde ein Mangling-Schema gebildet. Jeder Funktionsname wurde mit einer Zahl entstellt, die die Größe seiner Argumente in Bytes angibt. Also auf 16-Bit-Maschine die folgende Funktion (C-Syntax):
Wurde verstümmelt
function@2
, weilint
es zwei Bytes breit ist. Dies wurde durchgeführt, damit der Linker die Funktion nicht findet, wenn die Deklaration und Definition nicht übereinstimmen, anstatt den Stapel zur Laufzeit zu beschädigen. Wenn das Programm dagegen verknüpft ist, können Sie sicher sein, dass am Ende des Aufrufs die richtige Anzahl von Bytes aus dem Stapel entfernt wird.32-Bit-Windows und höher verwenden
stdcall
stattdessen das ABI. Es ähnelt dem Pascal ABI, außer dass die Push-Reihenfolge wie in C von rechts nach links ist. Wie beim Pascal ABI werden beim Durchführen des Namens die Byte-Größe der Argumente in den Funktionsnamen zerlegt, um eine Stapelbeschädigung zu vermeiden.Im Gegensatz zu Behauptungen, die an anderer Stelle hier gemacht wurden, werden die Funktionsnamen vom C ABI selbst in Visual Studio nicht entstellt. Umgekehrt sind Mangling-Funktionen, die mit der
stdcall
ABI-Spezifikation dekoriert sind, nicht nur für VS verfügbar. GCC unterstützt dieses ABI auch beim Kompilieren für Linux. Dies wird häufig von Wine verwendet, das einen eigenen Loader verwendet, um die Laufzeitverknüpfung von Linux-kompilierten Binärdateien mit Windows-kompilierten DLLs zu ermöglichen.quelle
C ++ - Compiler verwenden die Namensverwaltung, um eindeutige Symbolnamen für überladene Funktionen zuzulassen, deren Signatur ansonsten identisch wäre. Grundsätzlich werden auch die Arten von Argumenten codiert, was Polymorphismus auf funktionsbasierter Ebene ermöglicht.
C benötigt dies nicht, da es keine Überladung von Funktionen zulässt.
Beachten Sie, dass das Mangeln von Namen ein (aber sicherlich nicht der einzige!) Grund ist, warum man sich nicht auf ein 'C ++ ABI' verlassen kann.
quelle
C ++ möchte in der Lage sein, mit C-Code zu interagieren, der mit ihm verknüpft ist oder mit dem er verknüpft ist.
C erwartet nicht namengebundene Funktionsnamen.
Wenn C ++ es entstellt, findet es die exportierten nicht entstellten Funktionen von C nicht, oder C findet die exportierten Funktionen von C ++ nicht. Der C-Linker muss den Namen erhalten, den er selbst erwartet, da er nicht weiß, dass er von C ++ kommt oder zu C ++ geht.
quelle
Wenn Sie die Namen von C-Funktionen und -Variablen entstellen, können deren Typen zum Zeitpunkt der Verknüpfung überprüft werden. Derzeit können Sie in allen (?) C-Implementierungen eine Variable in einer Datei definieren und in einer anderen als Funktion aufrufen. Oder Sie können eine Funktion mit einer falschen Signatur deklarieren (z. B.
void fopen(double)
und dann aufrufen).Ich schlug 1991 ein Schema für die typsichere Verknüpfung von C-Variablen und -Funktionen durch Mangeln vor. Das Schema wurde nie übernommen, da dies, wie andere hier angemerkt haben, die Abwärtskompatibilität zerstören würde.
quelle