Warum hat ISO / ANSI C ++ nicht auf binärer Ebene standardisiert? Es gibt viele Portabilitätsprobleme mit C ++, die nur auf die fehlende Standardisierung auf Binärebene zurückzuführen sind.
Don Box schreibt, (aus seinem Buch Essential COM , Kapitel COM As A Better C ++ zitierend )
C ++ und Portabilität
Sobald die Entscheidung getroffen ist eine C ++ Klasse als DLL zu verteilen, ein mit einem konfrontiert die grundlegenden Schwächen der C ++ , das heißt, fehlende Standardisierung auf der binären Ebene . Obwohl das ISO / ANSI C ++ Draft Working Paper versucht zu kodifizieren, welche Programme kompiliert werden und welche semantischen Auswirkungen ihre Ausführung haben wird, wird nicht versucht, das binäre Laufzeitmodell von C ++ zu standardisieren. Dieses Problem tritt zum ersten Mal auf, wenn ein Client versucht, eine Verknüpfung mit der Importbibliothek der FastString-DLL aus einer anderen C ++ - Entwicklungsumgebung als der zum Erstellen der FastString-DLL verwendeten herzustellen.
Gibt es weitere Vorteile oder den Verlust dieser fehlenden binären Standardisierung?
Antworten:
Sprachen mit binärkompatibler kompilierter Form sind eine relativ neue Phase [*], zum Beispiel die JVM- und .NET-Laufzeit. C- und C ++ - Compiler geben normalerweise systemeigenen Code aus.
Der Vorteil ist, dass keine JIT, kein Bytecode-Interpreter, keine VM oder ähnliches erforderlich ist. Beispielsweise können Sie den Bootstrap-Code, der beim Systemstart ausgeführt wird, nicht als netten, portablen Java-Bytecode schreiben, es sei denn, der Computer kann Java-Bytecode nativ ausführen, oder Sie haben eine Art Konverter von Java in einen nicht-binär-kompatiblen nativen Code ausführbarer Code (theoretisch: nicht sicher, ob dies in der Praxis für Bootstrap-Code empfohlen werden kann). Sie könnten es mehr oder weniger in C ++ schreiben, wenn auch nicht in portablem C ++, selbst auf der Source-Ebene, da es viel mit magischen Hardware-Adressen zu tun hat.
Der Nachteil ist, dass nativer Code natürlich nur auf der Architektur ausgeführt wird, für die er kompiliert wurde, und die ausführbaren Dateien nur von einem Loader geladen werden können, der ihr ausführbares Format versteht, und nur mit anderen ausführbaren Dateien für dieselbe Architektur verknüpft und diese aufgerufen werden können und ABI.
Selbst wenn Sie so weit kommen, funktioniert das Verknüpfen zweier ausführbarer Dateien nur dann richtig, wenn: (a) Sie nicht gegen die Ein-Definition-Regel verstoßen. Dies ist einfach, wenn sie mit verschiedenen Compilern / Optionen / was auch immer kompiliert wurden. so, dass sie unterschiedliche Definitionen derselben Klasse verwendeten (entweder in einem Header oder weil sie jeweils statisch mit unterschiedlichen Implementierungen verknüpft waren); und (b) alle relevanten Implementierungsdetails, wie z. B. das Strukturlayout, sind gemäß den Compileroptionen, die bei der Kompilierung jeweils gültig waren, identisch.
Wenn der C ++ - Standard all dies definiert, würden viele der derzeit für Implementierer verfügbaren Freiheiten verloren gehen. Implementierer nutzen diese Freiheiten, insbesondere beim Schreiben von Code auf sehr niedriger Ebene in C ++ (und C, das das gleiche Problem aufweist).
Wenn Sie etwas schreiben möchten, das ein bisschen wie C ++ aussieht, gibt es für ein binär portierbares Ziel C ++ / CLI, das auf .NET und Mono abzielt, so dass Sie .NET (hoffentlich) anders als unter Windows ausführen können. Ich denke, es ist möglich, den MS-Compiler davon zu überzeugen, reine CIL-Assemblys zu erstellen, die unter Mono ausgeführt werden können.
Möglicherweise können auch Dinge mit LLVM ausgeführt werden, um eine binär portable C- oder C ++ - Umgebung zu erstellen. Ich weiß jedoch nicht, dass sich ein weit verbreitetes Beispiel herauskristallisiert hat.
Dies alles hängt jedoch davon ab, dass viele Dinge behoben werden, die von der Implementierung in C ++ abhängig sind (z. B. die Größe der Typen). Dann muss die Umgebung, die die tragbaren Binärdateien versteht, auf dem System verfügbar sein, auf dem der Code ausgeführt werden soll. Durch das Zulassen nicht portierbarer Binärdateien können C und C ++ an Stellen eingesetzt werden, an denen tragbare Binärdateien nicht möglich sind, und aus diesem Grund sagt der Standard überhaupt nichts über Binärdateien aus.
Dann auf einer bestimmten Plattform - Implementierungen in der Regel noch nicht bieten binäre Kompatibilität zwischen den verschiedenen Gruppen von Optionen, obwohl der Standard nicht , sie zu stoppen ist. Wenn es Don Box nicht gefällt, dass Microsofts Compiler inkompatible Binärdateien aus derselben Quelle produzieren können, ist es das Compilerteam, über das er sich beschweren muss. Die Sprache C ++ nicht verbieten einen Compiler oder ein Betriebssystem von Fesselung alle notwendigen Details, so , wenn Sie sich auf Windows beschränken , es ist kein grundsätzliches Problem mit C ++. Microsoft hat sich entschieden, dies nicht zu tun.
Die Unterschiede manifestieren sich oft als eine weitere Sache, die Sie falsch machen und Ihr Programm zum Absturz bringen können, aber es kann beträchtliche Effizienzgewinne geben, beispielsweise zwischen inkompatiblem Debug und Release-Versionen einer DLL.
[*] Ich bin mir nicht sicher, wann die Idee zum ersten Mal erfunden wurde, wahrscheinlich 1642 oder so, aber ihre aktuelle Beliebtheit ist relativ neu im Vergleich zu der Zeit, als C ++ sich auf die Entwurfsentscheidungen festgelegt hat, die verhindern, dass sie die Binärportabilität definieren.
quelle
Plattformübergreifende und compilerübergreifende Kompatibilität waren nicht die Hauptziele von C und C ++. Sie wurden in einer Ära geboren und für Zwecke gedacht, für die plattformspezifische und compilerspezifische Minimierungen von Zeit und Raum entscheidend waren.
Aus Stroustrups "Das Design und die Entwicklung von C ++":
quelle
Es ist kein Fehler, es ist ein Feature! Dies gibt Implementierern die Freiheit, ihre Implementierung auf binärer Ebene zu optimieren. Der Little-Endian i386 und seine Nachkommen sind nicht die einzigen CPUs, die es gibt oder gibt.
quelle
Das Problem im Angebote beschrieben wird durch die ganz bewusste Vermeidung von Standardisierung von Symbol-Namen Mangeln Systemen verursacht (ich glaube , „ Standardisierung auf der binären Ebene “ ist ein irreführender Begriff in diesem Zusammenhang , obwohl die Ausgabe zu einem Compiler verwandt Application Binary Interface ( ABI).
C ++ codiert die Signatur- und Typinformationen einer Funktion oder eines Datenobjekts sowie deren Klassen- / Namespacemitgliedschaft in den Symbolnamen. Verschiedene Compiler dürfen unterschiedliche Schemata verwenden. Folglich wird ein Symbol in einer statischen Bibliothek, DLL oder Objektdatei nicht mit Code verknüpft, der mit einem anderen Compiler (oder möglicherweise sogar einer anderen Version desselben Compilers) kompiliert wurde.
Das Problem wird wahrscheinlich besser beschrieben und erklärt als ich es hier kann , mit Beispielen für Schemata, die von verschiedenen Compilern verwendet werden.
Die Gründe für den absichtlichen Mangel an Standardisierung sind auch erklärt hier .
quelle
Das Ziel von ISO / ANSI war die Standardisierung der C ++ - Sprache, ein Problem, das so komplex zu sein scheint, dass Jahre erforderlich sind, um die Sprachstandards und die Compilerunterstützung zu aktualisieren.
Die Binärkompatibilität ist viel komplexer, da die Binärdateien auf verschiedenen CPU-Architekturen und verschiedenen Betriebssystemumgebungen ausgeführt werden müssen.
quelle
Wie Andy sagte, war die plattformübergreifende Kompatibilität kein großes Ziel, wohingegen eine breite Plattform- und Hardwareimplementierung ein Ziel war, mit dem Ergebnis, dass Sie konforme Implementierungen für eine sehr große Auswahl von Systemen schreiben können. Eine binäre Standardisierung hätte dies praktisch unmöglich gemacht.
Die C-Kompatibilität war ebenfalls wichtig und hätte dies erheblich erschwert.
In der Folge wurden einige Anstrengungen unternommen, um die ABI für eine Untergruppe von Implementierungen zu standardisieren .
quelle
Ich denke, das Fehlen eines Standards für C ++ ist ein Problem in der heutigen Welt der entkoppelten, modularen Programmierung. Wir müssen jedoch definieren, was wir von einem solchen Standard wollen.
Niemand, der bei klarem Verstand ist, möchte die Implementierung oder Plattform für eine Binärdatei definieren. Sie können eine x86-Windows-DLL also nicht auf einer x86_64-Linux-Plattform verwenden. Das wäre ein bisschen viel.
Was die Leute jedoch wollen, ist dasselbe, was wir mit C-Modulen haben - eine standardisierte Schnittstelle auf Binärebene (dh einmal kompiliert). Wenn Sie derzeit eine DLL in eine modulare App laden möchten, exportieren Sie C-Funktionen und binden sie zur Laufzeit an diese. Mit einem C ++ - Modul ist das nicht möglich. Es wäre großartig, wenn Sie könnten, was auch bedeuten würde, dass mit einem Compiler geschriebene DLLs von einem anderen geladen werden könnten. Sicher, Sie wären immer noch nicht in der Lage, eine für eine inkompatible Plattform erstellte DLL zu laden, aber das ist kein Problem, das behoben werden muss.
Wenn also der Standardkörper definiert, welche Schnittstelle ein Modul verfügbar macht, haben wir viel mehr Flexibilität beim Laden von C ++ - Modulen. Wir müssten C ++ - Code nicht als C-Code verfügbar machen, und wir würden wahrscheinlich viel mehr davon nutzen von C ++ in Skriptsprachen.
Wir müssten auch Dinge wie COM nicht leiden, die versuchen, eine Lösung für dieses Problem zu finden.
quelle
Ich denke nicht, dass es so einfach ist. Die Antworten liefern bereits hervorragende Gründe für die mangelnde Konzentration auf die Standardisierung, aber C ++ ist möglicherweise zu sprachenreich, um wirklich mit C als ABI-Standard konkurrieren zu können.
Wir können auf die Namensverfälschung eingehen, die sich aus Funktionsüberladung, Inkompatibilitäten von V-Tabellen, Inkompatibilitäten mit Ausnahmen über Modulgrenzen hinweg usw. ergibt. All dies ist ein echtes Problem, und ich wünschte, sie könnten zumindest die Layouts von V-Tabellen standardisieren.
Bei einem ABI-Standard geht es jedoch nicht nur darum, C ++ - Dylibs, die in einem Compiler erstellt wurden, so zu machen, dass sie von einer anderen Binärdatei verwendet werden können, die von einem anderen Compiler erstellt wurde. ABI wird sprachenübergreifend verwendet . Es wäre schön, wenn sie zumindest den ersten Teil abdecken könnten, aber ich sehe keine Möglichkeit, dass C ++ jemals wirklich mit C auf einer universellen ABI-Ebene konkurriert, die für die Herstellung der am weitesten kompatiblen Dylibs so wichtig ist.
Stellen Sie sich ein einfaches Funktionspaar vor, das wie folgt exportiert wird:
... und sich vorstellen ,
Foo
undBar
waren Klassen mit parametrisierte Konstrukteure, Kopierkonstruktoren, verschieben Bauer, und nicht-triviale Destruktoren.Nehmen Sie dann das Szenario eines Python / Lua / C # / Java / Haskell / etc. Entwickler, der versucht, dieses Modul zu importieren und in seiner Sprache zu verwenden.
Zuerst benötigen wir einen Namensverwaltungsstandard für den Export von Symbolen unter Verwendung von Funktionsüberladung. Dies ist ein einfacher Teil. Dabei sollte es eigentlich nicht "Mangeln" heißen. Da Benutzer der Dylib Symbole nach Namen suchen müssen, sollten die Überladungen hier zu Namen führen, die nicht wie ein vollständiges Durcheinander aussehen. Vielleicht könnten die Symbolnamen so
"f_Foo"
"f_Bar_int"
oder so ähnlich sein . Wir müssten sicherstellen, dass sie nicht mit einem vom Entwickler definierten Namen in Konflikt geraten können, und möglicherweise einige Symbole / Zeichen / Konventionen für die ABI-Verwendung reservieren.Aber jetzt ein schwierigeres Szenario. Wie ruft der Python-Entwickler beispielsweise Move-Konstruktoren, Copy-Konstruktoren und Destruktoren auf? Vielleicht könnten wir diese als Teil der Dylib exportieren. Was aber, wenn
Foo
undBar
in verschiedenen Modulen exportiert werden? Sollten wir die in dieser Dylib enthaltenen Symbole und Implementierungen duplizieren oder nicht? Ich würde vorschlagen, dass wir dies tun, da es sonst sehr schnell nervig werden könnte, sich in mehreren Dylib-Schnittstellen zu verfangen, nur um hier ein Objekt zu erstellen, es hier zu übergeben, eines dort zu kopieren, es hier zu zerstören. Während das gleiche grundlegende Anliegen in C (nur manuell / explizit) etwas zutreffen könnte, tendiert C dazu, dies aufgrund der Art und Weise, wie die Leute damit programmieren, zu vermeiden.Dies ist nur ein kleines Beispiel für die Unbeholfenheit. Was passiert, wenn eine der
f
oben genannten Funktionen eineBazException
(auch eine C ++ - Klasse mit Konstruktoren und Destruktoren und Ableitung der std :: exception) in JavaScript wirft ?Ich denke, wir können bestenfalls hoffen, ein ABI zu standardisieren, das von einer Binärdatei, die von einem C ++ - Compiler erzeugt wird, zu einer anderen Binärdatei, die von einem anderen erzeugt wird, funktioniert. Das wäre natürlich großartig, aber ich wollte nur darauf hinweisen. In der Regel geht mit solchen Überlegungen zur Verteilung einer generalisierten Bibliothek, die compilerübergreifend funktioniert, auch der Wunsch einher, eine wirklich generalisierte und kompatible sprachübergreifende Bibliothek zu erstellen.
Vorgeschlagene Lösung
Meine vorgeschlagene Lösung, nachdem ich jahrelang Mühe hatte, Möglichkeiten zu finden, C ++ - Schnittstellen für APIs / ABIs mit COM-ähnlichen Schnittstellen zu verwenden, besteht darin, einfach ein "C / C ++" - (Wortspiel-) Entwickler zu werden.
Verwenden Sie C, um diese universellen ABIs zu erstellen, mit C ++ für die Implementierung. Wir können weiterhin Dinge wie Exportfunktionen ausführen, die Zeiger auf undurchsichtige C ++ - Klassen mit expliziten Funktionen zurückgeben, um solche Objekte auf dem Heap zu erstellen und zu zerstören. Versuchen Sie, sich aus ABI-Sicht in diese C-Ästhetik zu verlieben, auch wenn wir C ++ für die Implementierung verwenden. Abstrakte Schnittstellen können mit Tabellen von Funktionszeigern modelliert werden. Es ist mühsam, dieses Zeug in eine C-API zu packen, aber die damit verbundenen Vorteile und die Kompatibilität der Distribution werden es in der Regel sehr lohnenswert machen.
Wenn wir diese Schnittstelle dann nicht so gerne direkt verwenden (wir sollten dies wahrscheinlich nicht aus RAII-Gründen tun), können wir alles, was wir wollen, in eine statisch verknüpfte C ++ - Bibliothek packen, die wir mit dem SDK ausliefern. C ++ Clients können das nutzen.
Python-Clients möchten weder eine C- noch eine C ++ - Schnittstelle direkt verwenden, da es keine Möglichkeit gibt, diese Pythonique zu erstellen. Sie werden es in ihre eigenen Pythonique-Interfaces packen wollen, also ist es eigentlich eine gute Sache, dass wir nur ein Minimum an C API / ABI exportieren, um das so einfach wie möglich zu machen.
Ich denke, ein Großteil der C ++ - Industrie würde davon mehr profitieren, als hartnäckig zu versuchen, Schnittstellen im COM-Stil und so weiter zu liefern. Es würde uns als Benutzer dieser Dylibs auch das ganze Leben leichter machen, uns nicht mit umständlichen ABIs herumschlagen zu müssen. C macht es einfach, und die Einfachheit aus ABI-Sicht ermöglicht es uns, APIs / ABIs zu erstellen, die für alle Arten von FFIs natürlich und minimalistisch funktionieren.
quelle
Ich weiß nicht, warum es nicht auf binärer Ebene standardisiert wird. Aber ich weiß, was ich dagegen mache. Unter Windows deklariere ich die Funktion extern "C" BOOL WINAPI. (Ersetzen Sie BOOL natürlich durch den Typ der Funktion.) Und sie werden sauber exportiert.
quelle
extern "C"
, wird das C ABI verwendet, ein De-facto- Standard für gängige PC-Hardware, auch wenn es von keinem Ausschuss vorgeschrieben wird.Verwenden
unzip foo.zip && make foo.exe && foo.exe
Sie diese Option, wenn Sie die Portabilität Ihrer Quelle wünschen.quelle