Wurden in C ++ 11 Probleme beim Übergeben von Standardbibliotheksobjekten zwischen dynamischen / gemeinsam genutzten Bibliotheksgrenzen behoben? (dh DLLs und so)?

34

Eine meiner Hauptbeschwerden über C ++ ist, wie schwierig es in der Praxis ist, Standardbibliotheksobjekte außerhalb der dynamischen Bibliotheksgrenzen (dll / so) zu übergeben.

Die Standardbibliothek enthält häufig nur Header. Das ist großartig, um einige großartige Optimierungen durchzuführen. Bei DLLs werden sie jedoch häufig mit unterschiedlichen Compilereinstellungen erstellt, die sich auf die interne Struktur / den Code eines Standardbibliothekscontainers auswirken können. Beispielsweise kann in MSVC eine DLL mit aktiviertem Iterator-Debug erstellt werden, während eine andere mit deaktiviertem Iterator erstellt wird. Diese beiden DLLs können Probleme beim Weitergeben von Standardcontainern verursachen. Wenn ich std::stringin meiner Benutzeroberfläche offenlege, kann ich nicht garantieren, dass der Code, für den der Client verwendet std::stringwird, genau mit dem meiner Bibliothek übereinstimmt std::string.

Dies führt dazu, dass Probleme, Kopfschmerzen usw. nur schwer behoben werden können. Sie steuern entweder die Compilereinstellungen in Ihrer Organisation streng, um diese Probleme zu vermeiden, oder Sie verwenden eine einfachere C-Schnittstelle, die diese Probleme nicht aufweist. Oder geben Sie Ihren Clients die erwarteten Compilereinstellungen an, die sie verwenden sollen (was schade ist, wenn eine andere Bibliothek andere Compilereinstellungen angibt).

Meine Frage ist, ob C ++ 11 versucht hat, diese Probleme zu lösen.

Doug T.
quelle
3
Ich kenne die Antwort auf Ihre Frage nicht, aber ich kann sagen, dass Ihre Bedenken geteilt werden. Sie sind ein Schlüssel dafür, warum ich C ++ nicht in meinen Projekten verwende, da wir Wert auf ABI-Stabilität legen, wenn es darum geht, jeden letzten Zyklus potenzieller Effizienz auszuschließen.
Donal Fellows
2
Bitte unterscheiden. Es ist schwer zwischen DLLs. Zwischendurch hat SOes immer prima geklappt.
Jan Hudec
1
Streng genommen handelt es sich hierbei nicht nur um ein C ++ - Problem. Es ist möglich, dass dieses Problem bei anderen Sprachen auftritt.
MrFox
2
@JanHudec Ich kann garantieren, dass zwischen SOs nicht annähernd so magisch funktioniert, wie Sie anscheinend anzeigen. Angesichts der Sichtbarkeit von Symbolen und der Art und Weise, wie die Namensverknüpfung häufig funktioniert, sind Sie möglicherweise besser von einem Problem isoliert, aber wenn Sie eine .so-Datei mit verschiedenen Flags / etc. Kompilieren und davon ausgehen, dass Sie sie in einem Programm mit anderen Flags verknüpfen können, ist dies ein Rezept für eine Katastrophe.
SDG
3
@sdg: Mit Standardflags und Standardsichtbarkeit funktioniert es. Wenn du sie änderst und in Schwierigkeiten gerätst, ist es dein Problem und das von niemand anderem.
Jan Hudec

Antworten:

20

Sie haben Recht, dass in einer öffentlichen C ++ - API alles, was als STL bezeichnet wird, dh alles, was aus einer Bibliothek eines Drittanbieters stammt, am besten vermieden wird. Sie möchten auch die lange Liste von Regeln unter http://www.ros.org/reps/rep-0009.html#definition befolgen , um ABI-Brüche zu verhindern, die das Programmieren öffentlicher C ++ - APIs zu einer lästigen Aufgabe machen.

Und die Antwort in Bezug auf C ++ 11 ist nein, dieser Standard berührt das nicht. Interessanter ist, warum nicht? Die Antwort liegt darin, dass C ++ 17 das sehr berührt, und damit C ++ - Module implementiert werden können, müssen exportierte Vorlagen funktionieren, und dafür benötigen wir einen LLVM-Compiler wie clang, der den vollständigen AST auf die CD und dann ausgeben kann Führen Sie aufruferabhängige Suchvorgänge durch, um die vielen Fälle von ODR-Verstößen in einem großen C ++ - Projekt zu behandeln - das übrigens viel GCC- und ELF-Code enthält.

Zuletzt sehe ich eine Menge MSVC-Hass- und Pro-GCC-Kommentare. Diese sind sehr falsch informiert - GCC auf ELF ist grundsätzlich und unwiederbringlich nicht in der Lage, gültigen und korrekten C ++ - Code zu erstellen. Die Gründe dafür sind zahlreich und zahlreich, aber ich werde schnell ein Fallbeispiel anführen: GCC unter ELF kann keine sicheren Python-Erweiterungen erzeugen, die mit Boost.Python geschrieben wurden, wobei mehr als eine auf Boost.Python basierende Erweiterung in Python geladen wird. Das liegt daran, dass ELF mit seiner globalen C-Symboltabelle nicht in der Lage ist, ODR-Verstöße, die zu Fehlern führen, zu verhindern, wohingegen PE und MachO sowie die vorgeschlagene C ++ - Modulspezifikation Symboltabellen pro Modul verwenden - was im Übrigen auch erheblich schnellere Prozessinitialisierungszeiten bedeutet. Und es gibt noch viel mehr Probleme: Sehen Sie sich einen StackOverflow an, auf den ich kürzlich geantwortet habehttps://stackoverflow.com/questions/14268736/symbol-visibility-exceptions-runtime-error/14364055#14364055 zum Beispiel, wenn C ++ - Ausnahmewürfe auf ELF unwiederbringlich grundlegend beschädigt sind.

Letzter Punkt: In Bezug auf das Interagieren verschiedener STLs ist dies ein großes Problem für viele große Unternehmensbenutzer, die versuchen, Bibliotheken von Drittanbietern zu mischen, die eng in eine STL-Implementierung integriert sind. Die einzige Lösung ist ein neuer Mechanismus für C ++, mit dem STL-Interop behandelt werden kann, und während C ++ gerade dabei ist, können Sie auch das Interop des Compilers reparieren, damit Sie beispielsweise kompilierte MSVC-, GCC- und Clang-Objektdateien mischen können und alles funktioniert . Ich würde mir die C ++ 17-Bemühungen ansehen und sehen, was sich in den nächsten Jahren dort abspielt - ich wäre überrascht, wenn nichts passiert.

Niall Douglas
quelle
Tolle Resonanz! Ich hoffe nur, dass Clang die Windows-Kompatibilität verbessert und möglicherweise einen guten Standard-Compiler setzt. Das textuelle Einschluss- / Header-System von C ++ ist schrecklich. Ich freue mich auf den Tag, an dem Module die C ++ - Code-Organisation vereinfachen, die Kompilierungszeiten unendlich beschleunigen und die Interoperabilität des Compilers mit ODR-verletzenden Catches verbessern.
Alessandro Stamatto
3
Persönlich erwarte ich tatsächlich eine erhebliche Verlängerung der Compilerzeiten. Das schnelle Durchlaufen eines modulinternen AST ist sehr schwierig, und wir benötigen wahrscheinlich einen gemeinsam genutzten Speichercache im Speicher. Fast alles andere, was schlecht ist, wird jedoch besser. Übrigens, Header-Dateien bleiben definitiv erhalten, die aktuellen C ++ - Module weisen eine 1-zu-1-Zuordnung von Schnittstellendateien zu Header-Dateien auf. Darüber hinaus sind automatisch generierte Schnittstellendateien in C ++ zulässig. In einem älteren Header werden C-Makros einfach herausgefiltert und als Schnittstellendateien ausgegeben. Schön, was?
Niall Douglas
Cool! Ich habe so viele Zweifel an Modulen. Berücksichtigt das Modulsystem Textuelle Inklusion vs. Symbolische Inklusion? Mit der vorliegenden include-Direktive muss der Compiler für jede Quelldatei immer wieder Zehntausende von Codezeilen neu kompilieren. Lässt das Modulsystem eines Tages Code ohne Vorwärtsdeklarationen zu? Verbessert / vereinfacht es die Erstellung von Tools?
Alessandro Stamatto
2
-1 für den Hinweis, dass alle Vorlagen von Drittanbietern verdächtig sind. Das Ändern der Konfiguration ist unabhängig davon, ob es sich bei dem zu konfigurierenden Objekt um eine Vorlage handelt.
DeadMG
1
@Alessandro: Die vorgeschlagenen C ++ - Module deaktivieren explizit C-Makros. Sie können Vorlagen oder nowt verwenden. Die vorgeschlagenen Schnittstellen sind legales C ++ und werden lediglich automatisch generiert. Sie können optional vorkompiliert werden, um das Reparieren zu beschleunigen. Erwarten Sie also keine Beschleunigung vorhandener vorkompilierter Header. Die letzten beiden Fragen weiß ich eigentlich nicht: es kommt darauf an :)
Niall Douglas
8

Die Spezifikation hatte dieses Problem nie. Das liegt daran, dass es ein Konzept mit der Bezeichnung "Eine Definitionsregel" gibt, das vorschreibt, dass jedes Symbol im laufenden Prozess genau eine Definition hat.

Windows-DLLs verletzen diese Anforderung. Deshalb gibt es all diese Probleme. Es liegt also an Microsoft, das Problem zu beheben, nicht am C ++ - Standardisierungsausschuss. Unix hatte dieses Problem nie, da gemeinsam genutzte Bibliotheken dort anders funktionieren und standardmäßig einer Definitionsregel entsprechen (Sie können es explizit brechen, aber Sie tun es offensichtlich nur, wenn Sie wissen, dass Sie es sich leisten können und die wenigen zusätzlichen Zyklen ausmerzen müssen).

Windows-DLLs verstoßen gegen eine Definitionsregel, weil:

  • Sie codieren fest, aus welcher dynamischen Bibliothek ein Symbol während der statischen Verbindungszeit verwendet wird, und lösen Symbole statisch in der Bibliothek auf, die sie definiert. Wenn also dasselbe schwache Symbol in mehreren gemeinsam genutzten Bibliotheken und in diesen Bibliotheken generiert wird, als es in einem einzelnen Prozess verwendet wird, hat der dynamische Linker keine Chance, diese Symbole zusammenzuführen. Normalerweise sind solche Symbole statische Elemente oder Klassenhemmnisse von Vorlageninstanzen und verursachen dann Probleme, wenn Instanzen zwischen Code in verschiedenen DLLs übertragen werden.
  • Sie kodieren fest, ob das Symbol bereits während der Kompilierung aus der dynamischen Bibliothek importiert wird. Daher ist Code, der mit einer Bibliothek statisch verknüpft ist, nicht mit Code kompatibel, der mit derselben Bibliothek dynamisch verknüpft ist.

Unix, das ELF-Formatexporte verwendet, importiert implizit alle exportierten Symbole, um das erste Problem zu vermeiden, und unterscheidet erst nach einer statischen Verbindungszeit zwischen statisch und dynamisch aufgelösten Symbolen, um das zweite zu vermeiden.


Das andere Problem sind Compiler-Flags. Dieses Problem tritt bei jedem Programm auf, das aus mehreren Kompilierungseinheiten besteht. Dynamische Bibliotheken müssen nicht beteiligt sein. Unter Windows ist es jedoch viel schlimmer. Unter Unix spielt es keine Rolle, ob Sie statisch oder dynamisch verknüpfen, niemand verknüpft die Standardlaufzeit statisch (unter Linux ist dies möglicherweise sogar illegal) und es gibt keine spezielle Debug-Laufzeit, sodass ein Build gut genug ist. Die Art und Weise, wie Microsoft statisches und dynamisches Verknüpfen, Debugging und Release-Laufzeit sowie einige andere Optionen implementierte, führte jedoch zu einer kombinatorischen Explosion der benötigten Bibliotheksvarianten. Wieder ein Problem mit der Plattform und nicht mit der C ++ - Sprache.

Jan Hudec
quelle
2
@DougT .: GCC hat nichts damit zu tun. Die Plattform ABI hat. In ELF, dem von den meisten Unices verwendeten Objektformat, exportieren gemeinsam genutzte Bibliotheken alle sichtbaren Symbole und importieren alle von ihnen exportierten Symbole. Wenn also etwas in mehreren Bibliotheken generiert wird, verwendet der dynamische Linker die erste Definition für alle. Einfach, elegant und funktionierend.
Jan Hudec
1
@MartinBa: Es gibt nichts zu verschmelzen, aber es spielt keine Rolle, solange es dasselbe ist und solange es überhaupt nicht verschmolzen werden soll. Ja, wenn Sie inkompatible Compilereinstellungen auf einer ELF-Plattform verwenden, wird das gleiche Chaos wie überall und überall verursacht. Auch wenn Sie keine gemeinsam genutzten Bibliotheken verwenden, ist dies hier nicht unbedingt ein Thema.
Jan Hudec
1
@Jan - es ist relevant für Ihre Antwort. Sie schreiben: "... eine Definitionsregel ... Windows-DLLs verletzen diese Anforderung ... gemeinsam genutzte Bibliotheken funktionieren anders [unter UNix] ...", aber die gestellte Frage bezieht sich auf Probleme mit std-lib-Inhalten (in Headern definiert) und der Grund, warum es unter Unix kein Problem gibt, hat nichts mit SO vs. DLL zu tun, sondern mit der Tatsache, dass es unter Unix (anscheinend) nur eine kompatible Version der Standardbibliothek gibt, während unter Windows MS inkompatible (Debug-) Versionen ausgewählt wurden (mit erweiterter Prüfung etc.).
Martin Ba
1
@MartinBa: Nein, der Hauptgrund für das Problem unter Windows ist, dass der unter Windows verwendete Export- / Importmechanismus statische Elemente und Klassenhindernisse von Vorlagenklassen nicht in allen Fällen ordnungsgemäß zusammenführen und keine statisch und dynamisch verknüpften Symbole zusammenführen kann. Die zahlreichen Bibliotheksvarianten haben dies noch viel schlimmer gemacht, aber das Hauptproblem besteht darin, dass C ++ Flexibilität durch den Linker benötigt, über den der dynamische Linker von Windows nicht verfügt.
Jan Hudec
4
Ich denke, dass diese Implikation, dass die DLL-Spezifikation gebrochen ist und die entsprechende Forderung von Msft, es zu beheben, falsch ist. Die Tatsache, dass DLLs bestimmte Funktionen von C ++ nicht unterstützen, ist kein Fehler der DLL-Spezifikation. DLLs sind sprachneutrale, herstellerneutrale Paketierungsmechanismen und ABI, um Einstiegspunkte für Maschinencode ('Funktionsaufrufe') und Datenblobs bereitzustellen. Sie sollten niemals fortgeschrittene Funktionen einer bestimmten Sprache unterstützen. Es ist nicht die Schuld von MSFT oder der DLL, dass manche Leute wollen, dass sie etwas anderes sind.
Euro Micelli
6

Nein.

Es wird viel daran gearbeitet, das Headersystem zu ersetzen, eine Funktion, die als Module bezeichnet wird und einen Einfluss darauf haben könnte, aber sicherlich keinen großen.

Klaim
quelle
2
Ich denke nicht, dass das Header-System einen Einfluss darauf haben würde. Die Probleme sind, dass Windows-DLLs eine Definitionsregel verletzen (was bedeutet, dass sie der C ++ - Spezifikation nicht folgen, sodass C ++ - Komitee nichts dagegen unternehmen kann) und dass es in Windows so viele Varianten der Standardlaufzeit gibt, wie C ++ - Komitee kann. Ich tue auch nichts dagegen.
Jan Hudec
1
Nein, das tun sie nicht. Wie konnten sie, die Spezifikation erwähnt nicht einmal etwas dieser Art. Wenn ein (Windows-) Programm mit Windows-DLLs verknüpft ist, ist der ODR im Übrigen erfüllt: Alle sichtbaren (exportierten) Symbole müssen dem ODR entsprechen.
Paul Michalik
@PaulMichalik C ++ deckt das Verknüpfen ab (Phase 9) und es scheint mir, dass mindestens das Verknüpfen von DLLs / SOs während des Ladevorgangs in Phase 9 fällt. Dies bedeutet, dass Symbole mit externer Verknüpfung (ob exportiert oder nicht) verknüpft und konform sein sollten die ODR. Die dynamische Verknüpfung mit LoadLibrary / dlopen fällt offensichtlich nicht unter diese Anforderungen.
Bames53
@ bames53: IMHO, die Angaben sind viel zu schwach, um Aussagen dieser Art zuzulassen. Eine .dll / .so kann als eigenständiges "Programm" angesehen werden. Dann waren die Regeln erfüllt. So etwas wie das Laden anderer "Programme" zur Laufzeit ist vom Standard her so wenig spezifiziert, dass alle diesbezüglichen Aussagen ziemlich willkürlich sind.
Paul Michalik
@PaulMichalik Wenn für eine ausführbare Datei eine Ladezeitverknüpfung erforderlich ist, bleiben vor der Ladezeitverknüpfung externe Entitäten ungelöst, und für die Ausführung erforderliche Informationen fehlen. LoadLibrary und dlopen liegen außerhalb der Spezifikation, aber das Verknüpfen der Ladezeit muss eindeutig Teil von Phase 9 sein.
bames53