Ist es sicher, C ++ 17-, C ++ 14- und C ++ 11-Objekte zu verknüpfen?

96

Angenommen, ich habe drei kompilierte Objekte, die alle von demselben Compiler / derselben Version erstellt wurden :

  1. A wurde mit dem C ++ 11-Standard kompiliert
  2. B wurde mit dem C ++ 14-Standard kompiliert
  3. C wurde mit dem C ++ 17-Standard kompiliert

Nehmen wir der Einfachheit halber an, dass alle Header in C ++ 11 geschrieben wurden und nur Konstrukte verwendet wurden, deren Semantik sich zwischen allen drei Standardversionen nicht geändert hat. Daher wurden alle Abhängigkeiten mit der Einbeziehung von Headern korrekt ausgedrückt und der Compiler hatte keine Einwände.

Um welche Kombinationen dieser Objekte handelt es sich und ist es nicht sicher, eine einzige Binärdatei zu verknüpfen? Warum?


BEARBEITEN: Antworten zu wichtigen Compilern (z. B. gcc, clang, vs ++) sind willkommen

Ricab
quelle
6
Keine Schul- / Interviewfrage. Die Frage stammt aus einem bestimmten Fall: Ich arbeite an einem Projekt, das von einer Open-Source-Bibliothek abhängt. Ich erstelle diese Bibliothek aus dem Quellcode, aber das Build-System akzeptiert nur ein Flag, um zwischen C ++ 03 / C ++ 11-Erstellung zu wählen. Der von mir verwendete Compiler unterstützt jedoch andere Standards, und ich erwäge, mein eigenes Projekt auf C ++ 17 zu aktualisieren. Ich bin mir nicht sicher, ob es eine sichere Entscheidung ist. Kann es eine Unterbrechung des ABI oder eine andere Art und Weise geben, in der der Ansatz nicht ratsam ist? Ich fand keine klare Antwort und beschloss, eine Frage zum allgemeinen Fall zu stellen.
Ricab
6
Dies hängt vollständig vom Compiler ab. Die formalen C ++ - Spezifikationen enthalten nichts, was diese Situation regelt. Es besteht auch eine geringe Möglichkeit, dass Code, der nach C ++ 03- oder C + 11-Standards geschrieben wurde, auf C ++ 14- und C ++ 17-Ebene Probleme aufweist. Mit ausreichendem Wissen und Erfahrung (und zunächst gut geschriebenem Code) sollte es möglich sein, eines dieser Probleme zu beheben. Wenn Sie jedoch mit den neueren C ++ - Standards nicht sehr vertraut sind, sollten Sie sich besser an das halten, was das Build-System unterstützt, und es wurde getestet, um damit zu arbeiten.
Sam Varshavchik
9
@Someprogrammerdude: Es ist eine äußerst lohnende Frage. Ich wünschte ich hätte eine Antwort. Ich weiß nur, dass libstdc ++ über RHEL devtoolset vom Design her abwärtskompatibel ist, indem es die neueren Inhalte statisch verknüpft und die älteren Inhalte zur Laufzeit mithilfe der "nativen" libstdc ++ der Distribution dynamisch auflöst. Aber das beantwortet die Frage nicht.
Leichtigkeitsrennen im Orbit
3
@nm: ... was meistens der Fall ist ... so ziemlich jeder, der verteilungsunabhängige C ++ - Bibliotheken verteilt, tut dies (1) in dynamischer Bibliotheksform und (2) ohne C ++ - Standardbibliothekscontainer an Schnittstellengrenzen. Bibliotheken, die aus einer Linux-Distribution stammen, haben es einfach, da sie alle mit demselben Compiler, derselben Standardbibliothek und nahezu demselben Standardsatz von Flags erstellt wurden.
Matteo Italia
3
Nur um den früheren Kommentar von @MatteoItalia zu verdeutlichen "und beim Wechsel von C ++ 03 in den C ++ 11-Modus (insbesondere std :: string)." Dies ist nicht wahr, die aktive std::stringImplementierung in libstdc ++ ist unabhängig vom verwendeten -stdModus . Dies ist eine wichtige Eigenschaft, genau zu Unterstützung Situationen wie die OPs. Sie können den neuen std::stringCode in C ++ 03 und den alten std::stringCode in C ++ 11 verwenden (siehe den Link in Matteos späterem Kommentar).
Jonathan Wakely

Antworten:

116

Um welche Kombinationen dieser Objekte handelt es sich und ist es nicht sicher, eine einzige Binärdatei zu verknüpfen? Warum?

Für GCC ist es sicher, eine beliebige Kombination von Objekten A, B und C miteinander zu verknüpfen. Wenn sie alle mit derselben Version erstellt wurden, sind sie ABI-kompatibel. Die Standardversion (dh die -stdOption) macht keinen Unterschied.

Warum? Denn das ist eine wichtige Eigenschaft unserer Implementierung, an deren Sicherstellung wir hart arbeiten.

Wenn Sie Probleme haben, verknüpfen Sie Objekte, die mit verschiedenen Versionen von GCC kompiliert wurden, und Sie haben instabile Funktionen aus einem neuen C ++ - Standard verwendet, bevor die Unterstützung von GCC für diesen Standard abgeschlossen ist. Wenn Sie beispielsweise ein Objekt mit GCC 4.9 -std=c++11und ein anderes Objekt mit GCC 5 kompilieren, treten -std=c++11Probleme auf. Die C ++ 11-Unterstützung war in GCC 4.x experimentell, und daher gab es inkompatible Änderungen zwischen den GCC 4.9- und 5-Versionen der C ++ 11-Funktionen. Wenn Sie ein Objekt mit GCC 7 und ein -std=c++17anderes Objekt mit GCC 8 kompilieren und -std=c++17Probleme haben, treten Probleme auf, da die C ++ 17-Unterstützung in GCC 7 und 8 noch experimentell ist und sich weiterentwickelt.

Auf der anderen Seite funktioniert jede Kombination der folgenden Objekte (siehe Hinweis zur libstdc++.soVersion unten ):

  • Objekt D kompiliert mit GCC 4.9 und -std=c++03
  • Objekt E kompiliert mit GCC 5 und -std=c++11
  • Objekt F kompiliert mit GCC 7 und -std=c++17

Dies liegt daran, dass die C ++ 03-Unterstützung in allen drei verwendeten Compilerversionen stabil ist und die C ++ 03-Komponenten daher zwischen allen Objekten kompatibel sind. Die C ++ 11-Unterstützung ist seit GCC 5 stabil, aber Objekt D verwendet keine C ++ 11-Funktionen, und die Objekte E und F verwenden beide Versionen, bei denen die C ++ 11-Unterstützung stabil ist. Die C ++ 17-Unterstützung ist in keiner der verwendeten Compilerversionen stabil, aber nur Objekt F verwendet C ++ 17-Funktionen. Daher gibt es keine Kompatibilitätsprobleme mit den beiden anderen Objekten (die einzigen Funktionen, die sie gemeinsam nutzen, stammen aus C ++ 03 oder C ++ 11, und die verwendeten Versionen machen diese Teile OK). Wenn Sie später ein viertes Objekt, G, mit GCC 8 -std=c++17kompilieren möchten, müssen Sie F mit derselben Version neu kompilieren (oder nicht mit F verknüpfen), da die C ++ 17-Symbole in F und G nicht kompatibel sind.

Die einzige Einschränkung für die oben beschriebene Kompatibilität zwischen D, E und F besteht darin, dass Ihr Programm die libstdc++.sogemeinsam genutzte Bibliothek von GCC 7 (oder höher) verwenden muss. Da Objekt F mit GCC 7 kompiliert wurde, müssen Sie die gemeinsam genutzte Bibliothek aus dieser Version verwenden, da beim Kompilieren eines Teils des Programms mit GCC 7 Abhängigkeiten von Symbolen entstehen können, die in libstdc++.soGCC 4.9 oder GCC 5 nicht vorhanden sind. Wenn Sie mit dem mit GCC 8 erstellten Objekt G verknüpft sind, müssen Sie das libstdc++.sovon GCC 8 verwenden, um sicherzustellen, dass alle von G benötigten Symbole gefunden werden. Die einfache Regel besteht darin, sicherzustellen, dass die gemeinsam genutzte Bibliothek, die das Programm zur Laufzeit verwendet, mindestens so neu ist wie die Version, die zum Kompilieren eines der Objekte verwendet wird.

Eine weitere Einschränkung bei der Verwendung von GCC, die bereits in den Kommentaren zu Ihrer Frage erwähnt wurde, ist, dass seit GCC 5 zwei Implementierungen vonstd::string in libstdc ++ verfügbar sind. Die beiden Implementierungen sind nicht verlinkungskompatibel (sie haben unterschiedliche verstümmelte Namen, können also nicht miteinander verknüpft werden), können jedoch in derselben Binärdatei nebeneinander existieren (sie haben unterschiedliche verstümmelte Namen, also keine Konflikte, wenn ein Objekt std::stringund das verwendet werden andere Verwendungen std::__cxx11::string). Wenn Ihre Objekte verwendet werden std::string, sollten sie normalerweise alle mit derselben Zeichenfolgenimplementierung kompiliert werden. Kompilieren Sie mit -D_GLIBCXX_USE_CXX11_ABI=0, um die ursprüngliche gcc4-compatibleImplementierung oder -D_GLIBCXX_USE_CXX11_ABI=1die neue cxx11Implementierung auszuwählen (lassen Sie sich nicht vom Namen täuschen, es kann auch in C ++ 03 verwendet werden, es heißtcxx11weil es den C ++ 11-Anforderungen entspricht). Welche Implementierung der Standard ist, hängt davon ab, wie GCC konfiguriert wurde. Der Standard kann jedoch beim Kompilieren mit dem Makro immer überschrieben werden.

Jonathan Wakely
quelle
"Weil das Kompilieren eines Teils des Programms mit GCC 7 zu Abhängigkeiten von Symbolen führen kann, die in libstdc ++ vorhanden sind. Ab GCC 4.9 oder GCC 5" haben Sie damit gemeint, dass NICHT ab GCC 4.9 oder GCC 5 vorhanden sind, oder? Gilt das auch für statische Verknüpfungen? Vielen Dank für die Informationen zur Kompatibilität zwischen Compilerversionen.
Hadi Brais
1
Ich habe gerade einen massiven Fehler festgestellt, als ich ein Kopfgeld für diese Frage angeboten habe. 😂
Leichtigkeitsrennen im Orbit
4
@ricab Ich bin mir zu 90% sicher, dass die Antwort für Clang / libc ++ dieselbe ist, aber ich habe keine Ahnung von MSVC.
Jonathan Wakely
1
Diese Antwort ist hervorragend. Ist irgendwo dokumentiert, dass 5.0+ für 11/14 stabil ist?
Barry
1
Nicht sehr klar oder an einem Ort. gcc.gnu.org/gcc-5/changes.html#libstdcxx und gcc.gnu.org/onlinedocs/libstdc++/manual/api.html#api.rel_51 erklären die Bibliotheksunterstützung für C ++ 11 für vollständig (die Sprache) Die Unterstützung war früher vollständig, aber immer noch "experimentell". Die Unterstützung der C ++ 14-Bibliothek wird bis 6.1 immer noch als experimentell aufgeführt, aber ich denke, in der Praxis hat sich zwischen 5.x und 6.x nichts geändert, was sich auf ABI auswirkt.
Jonathan Wakely
16

Die Antwort besteht aus zwei Teilen. Kompatibilität auf Compilerebene und Kompatibilität auf Linker-Ebene. Beginnen wir mit dem ersteren.

Nehmen wir an, alle Header wurden in C ++ 11 geschrieben

Die Verwendung desselben Compilers bedeutet, dass unabhängig vom C ++ - Zielstandard derselbe Standardbibliotheksheader und die gleichen Quelldateien (die dem Compiler zugeordneten Onces) verwendet werden. Daher werden die Header-Dateien der Standardbibliothek so geschrieben, dass sie mit allen vom Compiler unterstützten C ++ - Versionen kompatibel sind.

Wenn jedoch die zum Kompilieren einer Übersetzungseinheit verwendeten Compileroptionen einen bestimmten C ++ - Standard angeben, sollten auf alle Funktionen, die nur in neueren Standards verfügbar sind, nicht zugegriffen werden können. Dies geschieht mit der __cplusplusDirektive. In der Vektorquelldatei finden Sie ein interessantes Beispiel für die Verwendung. Ebenso lehnt der Compiler alle syntaktischen Funktionen ab, die neuere Versionen des Standards bieten.

All dies bedeutet, dass Ihre Annahme nur für die von Ihnen geschriebenen Header-Dateien gelten kann. Diese Header-Dateien können Inkompatibilitäten verursachen, wenn sie in verschiedenen Übersetzungseinheiten enthalten sind, die auf unterschiedliche C ++ - Standards abzielen. Dies wird in Anhang C des C ++ - Standards erörtert. Es gibt 4 Klauseln, ich werde nur die erste diskutieren und den Rest kurz erwähnen.

C.3.1 Abschnitt 2: lexikalische Konventionen

Einfache Anführungszeichen begrenzen ein Zeichenliteral in C ++ 11, während sie in C ++ 14 und C ++ 17 Zifferntrennzeichen sind. Angenommen, Sie haben die folgende Makrodefinition in einer der reinen C ++ 11-Headerdateien:

#define M(x, ...) __VA_ARGS__

// Maybe defined as a field in a template or a type.
int x[2] = { M(1'2,3'4) };

Betrachten Sie zwei Übersetzungseinheiten, die die Header-Datei enthalten, jedoch auf C ++ 11 bzw. C ++ 14 abzielen. Bei der Ausrichtung auf C ++ 11 wird das Komma in Anführungszeichen nicht als Parametertrennzeichen betrachtet. Es gibt nur einen Parameter. Daher wäre der Code äquivalent zu:

int x[2] = { 0 }; // C++11

Wenn Sie dagegen auf C ++ 14 abzielen, werden die einfachen Anführungszeichen als Zifferntrennzeichen interpretiert. Daher wäre der Code äquivalent zu:

int x[2] = { 34, 0 }; // C++14 and C++17

Der Punkt hier ist, dass die Verwendung von einfachen Anführungszeichen in einer der reinen C ++ 11-Headerdateien zu überraschenden Fehlern in den Übersetzungseinheiten führen kann, die auf C ++ 14/17 abzielen. Selbst wenn eine Header-Datei in C ++ 11 geschrieben ist, muss sie daher sorgfältig geschrieben werden, um sicherzustellen, dass sie mit späteren Versionen des Standards kompatibel ist. Die __cplusplusRichtlinie kann hier nützlich sein.

Die anderen drei Klauseln des Standards umfassen:

C.3.2 Abschnitt 3: Grundbegriffe

Änderung : Neuer üblicher (nicht platzierter) Deallocator

Begründung : Erforderlich für die Freigabe von Größen.

Auswirkung auf die ursprüngliche Funktion : Gültiger C ++ 2011-Code kann eine globale Platzierungszuweisungsfunktion und eine Freigabefunktion wie folgt deklarieren:

void operator new(std::size_t, std::size_t); 
void operator delete(void*, std::size_t) noexcept;

In dieser internationalen Norm kann die Erklärung zum Löschen von Operatoren jedoch mit einem vordefinierten üblichen Löschen von Operatoren (ohne Platzierung) (3.7.4) übereinstimmen. In diesem Fall ist das Programm wie bei Zuordnungsfunktionen für Klassenmitglieder und Freigabefunktionen (5.3.4) fehlerhaft.

C.3.3 Abschnitt 7: Erklärungen

Änderung : Nicht statische Elementfunktionen von constexpr sind nicht implizit konstante Elementfunktionen.

Begründung : Erforderlich, damit constexpr-Mitgliedsfunktionen das Objekt mutieren können.

Auswirkung auf die ursprüngliche Funktion : Gültiger C ++ 2011-Code kann in diesem internationalen Standard möglicherweise nicht kompiliert werden.

Der folgende Code ist beispielsweise in C ++ 2011 gültig, in diesem internationalen Standard jedoch ungültig, da dieselbe Elementfunktion zweimal mit unterschiedlichen Rückgabetypen deklariert wird:

struct S {
constexpr const int &f();
int &f();
};

C.3.4 Abschnitt 27: Eingabe- / Ausgabebibliothek

Änderung : get ist nicht definiert.

Begründung : Die Verwendung von Gets wird als gefährlich angesehen.

Auswirkung auf die ursprüngliche Funktion : Gültiger C ++ 2011-Code, der die Funktion gets verwendet, kann in diesem internationalen Standard möglicherweise nicht kompiliert werden.

Mögliche Inkompatibilitäten zwischen C ++ 14 und C ++ 17 werden in C.4 erläutert. Da alle nicht standardmäßigen Header-Dateien in C ++ 11 geschrieben sind (wie in der Frage angegeben), treten diese Probleme nicht auf, sodass ich sie hier nicht erwähnen werde.

Jetzt werde ich die Kompatibilität auf Linker-Ebene diskutieren. Mögliche Gründe für Inkompatibilitäten sind im Allgemeinen:

  • Das Format der Objektdateien.
  • Programmstart- und -abschlussroutinen und der mainEinstiegspunkt.
  • Gesamtprogrammoptimierung (WPO).

Wenn das Format der resultierenden Objektdatei vom C ++ - Zielstandard abhängt, muss der Linker in der Lage sein, die verschiedenen Objektdateien zu verknüpfen. In GCC, LLVM und VC ++ ist dies glücklicherweise nicht der Fall. Das heißt, das Format der Objektdateien ist unabhängig vom Zielstandard dasselbe, obwohl es stark vom Compiler selbst abhängt. Tatsächlich erfordert keiner der Linker von GCC, LLVM und VC ++ Kenntnisse über den Ziel-C ++ - Standard. Dies bedeutet auch, dass wir bereits kompilierte Objektdateien verknüpfen können (statische Verknüpfung der Laufzeit).

Wenn die Programmstartroutine (die aufrufende Funktion main) für verschiedene C ++ - Standards unterschiedlich ist und die verschiedenen Routinen nicht miteinander kompatibel sind, ist es nicht möglich, die Objektdateien zu verknüpfen. In GCC, LLVM und VC ++ ist dies glücklicherweise nicht der Fall. Darüber hinaus ist die Signatur der mainFunktion (und die für sie geltenden Einschränkungen, siehe Abschnitt 3.6 des Standards) in allen C ++ - Standards gleich, sodass es keine Rolle spielt, in welcher Übersetzungseinheit sie vorhanden ist.

Im Allgemeinen funktioniert WPO möglicherweise nicht gut mit Objektdateien, die mit unterschiedlichen C ++ - Standards kompiliert wurden. Dies hängt genau davon ab, welche Phasen des Compilers Kenntnisse des Zielstandards erfordern und welche nicht, und welche Auswirkungen dies auf prozedurale Optimierungen hat, die objektdateienübergreifend sind. Glücklicherweise sind GCC, LLVM und VC ++ gut gestaltet und haben dieses Problem nicht (das ist mir nicht bekannt).

Daher wurden GCC, LLVM und VC ++ entwickelt, um die Binärkompatibilität zwischen verschiedenen Versionen des C ++ - Standards zu ermöglichen. Dies ist jedoch nicht wirklich eine Anforderung des Standards selbst.

By the way, obwohl der VC ++ Compiler bietet die std Schalter , mit dem Sie eine bestimmte Version des C ++ Standard Ziel ermöglicht, unterstützt es nicht Targeting C ++ 11. Die Mindestversion, die angegeben werden kann, ist C ++ 14, die Standardeinstellung ab Visual C ++ 2013 Update 3. Sie könnten eine ältere Version von VC ++ verwenden, um auf C ++ 11 abzuzielen, müssten dann jedoch andere VC ++ - Compiler verwenden verschiedene Übersetzungseinheiten zu kompilieren, die auf verschiedene Versionen des C ++ - Standards abzielen, die zumindest WPO beschädigen würden.

CAVEAT: Meine Antwort ist möglicherweise nicht vollständig oder sehr präzise.

Hadi Brais
quelle
Die Frage sollte eigentlich eher das Verknüpfen als das Zusammenstellen betreffen. Ich erkenne (dank dieses Kommentars ), dass dies möglicherweise nicht klar war, und habe es bearbeitet, um deutlich zu machen, dass alle enthaltenen Überschriften in allen drei Standards dieselbe Interpretation haben.
Ricab
@ricab Die Antwort umfasst sowohl das Kompilieren als auch das Verknüpfen. Ich dachte du fragst nach beidem.
Hadi Brais
1
In der Tat, aber ich finde die Antwort viel zu lang und verwirrend, insbesondere bis "Jetzt werde ich die Kompatibilität auf Linker-Ebene diskutieren". Sie könnten alles darüber hinaus durch etwas ersetzen, bei dem nicht postuliert werden kann, dass die enthaltenen Header in C ++ 11 und C ++ 14/17 dieselbe Bedeutung haben. Dann ist es nicht sicher, sie überhaupt einzuschließen . Haben Sie für den verbleibenden Teil eine Quelle, aus der hervorgeht, dass diese drei Aufzählungspunkte die einzigen möglichen Gründe für Inkompatibilität sind? Vielen Dank für die Antwort auf jeden Fall, ich
stimme
@ricab kann ich nicht sicher sagen. Deshalb habe ich den Vorbehalt am Ende der Antwort hinzugefügt. Jeder andere kann die Antwort gerne erweitern, um sie präziser oder vollständiger zu machen, falls ich etwas verpasst habe.
Hadi Brais
Das verwirrt mich: "Die Verwendung des gleichen Compilers bedeutet, dass der gleiche Standard-Bibliotheksheader und die gleichen Quelldateien (...) verwendet werden." Wie kann das der Fall sein? Wenn ich alten Code mit gcc5 kompiliert habe, können die 'Compiler-Dateien', die zu dieser Version gehörten, nicht zukunftssicher sein. Bei Quellcode, der zu (wild) unterschiedlichen Zeiten mit unterschiedlichen Compilerversionen kompiliert wurde, können wir ziemlich sicher sein, dass der Bibliotheksheader und die Quelldateien unterschiedlich sind. Mit Ihrer Regel, dass diese gleich sein sollten, müssen Sie älteren Quellcode mit gcc5 neu kompilieren, ... und sicherstellen, dass alle die neuesten (gleichen) 'Compiler-Dateien' verwenden.
user2943111
2

Neue C ++ - Standards bestehen aus zwei Teilen: Sprachfunktionen und Standardbibliothekskomponenten.

Wie Sie unter neuem Standard verstehen , gibt es Änderungen in der Sprache selbst (z. B. für bestimmte Bereiche) fast kein Problem (manchmal bestehen Konflikte in Bibliotheksheadern von Drittanbietern mit neueren Standardsprachenfunktionen).

Aber Standardbibliothek ...

Jede Compilerversion enthält eine Implementierung der C ++ - Standardbibliothek (libstdc ++ mit gcc, libc ++ mit clang, MS C ++ - Standardbibliothek mit VC ++, ...) und genau eine Implementierung, nicht viele Implementierungen für jede Standardversion. In einigen Fällen können Sie auch eine andere Implementierung der Standardbibliothek als den bereitgestellten Compiler verwenden. Was Sie beachten sollten, ist die Verknüpfung einer älteren Standardbibliotheksimplementierung mit einer neueren.

Der Konflikt, der zwischen Bibliotheken von Drittanbietern und Ihrem Code auftreten kann, ist die Standardbibliothek (und andere Bibliotheken), die mit diesen Bibliotheken von Drittanbietern verknüpft ist.

E. Vakili
quelle
"Jede Compiler-Version enthält eine Implementierung von STL" Nein, das tun sie nicht
Lightness Races im Orbit
@LightnessRacesinOrbit Meinst du, dass es keine Beziehung zwischen zB libstdc ++ und gcc gibt?
E. Vakili
8
Nein, ich meine, die STL ist seit etwas mehr als zwanzig Jahren praktisch veraltet. Sie meinten die C ++ Standard Library. Können Sie für den Rest der Antwort einige Referenzen / Beweise vorlegen, um Ihre Behauptung zu stützen? Ich denke für eine Frage wie diese ist es wichtig.
Leichtigkeitsrennen im Orbit
3
Entschuldigung, nein, das geht aus dem Text nicht hervor. Sie haben einige interessante Aussagen gemacht, diese aber noch nicht mit Beweisen belegt.
Leichtigkeitsrennen im Orbit