Est-il sûr de lier des objets C ++ 17, C ++ 14 et C ++ 11

97

Supposons que j'ai trois objets compilés, tous produits par le même compilateur / version :

  1. A a été compilé avec le standard C ++ 11
  2. B a été compilé avec le standard C ++ 14
  3. C a été compilé avec le standard C ++ 17

Pour plus de simplicité, supposons que tous les en-têtes ont été écrits en C ++ 11, en utilisant uniquement des constructions dont la sémantique n'a pas changé entre les trois versions standard , et ainsi toutes les interdépendances ont été correctement exprimées avec l'inclusion d'en-tête et le compilateur n'a pas objecté.

Quelles combinaisons de ces objets est-il et n'est-il pas sûr de se lier à un seul binaire? Pourquoi?


EDIT: les réponses couvrant les principaux compilateurs (par exemple gcc, clang, vs ++) sont les bienvenues

ricab
la source
6
Pas une question d'école / d'entrevue. La question découle d'un cas particulier: je travaille sur un projet qui dépend d'une bibliothèque open-source. Je construis cette bibliothèque à partir des sources, mais son système de construction n'accepte qu'un indicateur pour choisir entre la construction C ++ 03 / C ++ 11. Le compilateur que j'utilise prend cependant en charge d'autres normes, et j'envisage de mettre à niveau mon propre projet vers C ++ 17. Je ne sais pas si c'est une décision sûre. Peut-il y avoir une interruption de l'ABI ou une autre manière dont l'approche n'est pas recommandée? Je n'ai pas trouvé de réponse claire et j'ai décidé de poster une question sur le cas général.
ricab
6
Cela dépend entièrement du compilateur. Il n'y a rien dans les spécifications formelles du C ++ qui régit cette situation. Il y a également une petite possibilité que le code qui a été écrit aux normes C ++ 03 ou C + 11 ait des problèmes au niveau C ++ 14 et C ++ 17. Avec des connaissances et une expérience suffisantes (et un code bien écrit pour commencer), il devrait être possible de résoudre l'un de ces problèmes. Cependant, si vous n'êtes pas très familier avec les nouvelles normes C ++, vous feriez mieux de vous en tenir à ce que le système de construction prend en charge et avec lequel il est testé.
Sam Varshavchik
9
@Someprogrammerdude: C'est une question extrêmement intéressante. J'aurais aimé avoir une réponse. Tout ce que je sais, c'est que libstdc ++ via RHEL devtoolset est rétrocompatible de par sa conception, en liant statiquement les éléments les plus récents et en laissant les éléments les plus anciens se résoudre dynamiquement au moment de l'exécution en utilisant la libstdc ++ "native" de la distribution. Mais cela ne répond pas à la question.
Courses de légèreté en orbite
3
@nm: ... ce qui est surtout le cas ... à peu près tout le monde qui distribue des bibliothèques C ++ indépendantes de la distribution le fait (1) sous forme de bibliothèque dynamique et (2) sans conteneurs de bibliothèque standard C ++ sur les limites de l'interface. Les bibliothèques qui proviennent d'une distribution Linux ont la tâche facile car elles sont toutes construites avec le même compilateur, la même bibliothèque standard et à peu près le même ensemble d'indicateurs par défaut.
Matteo Italia
3
Juste pour clarifier le commentaire précédent de @MatteoItalia "et lors du passage du mode C ++ 03 au mode C ++ 11 (std :: string en particulier)." Ce n'est pas vrai, l' std::stringimplémentation active dans libstdc ++ est indépendante du -stdmode utilisé . C'est une propriété importante, précisément pour supporter des situations comme les PO. Vous pouvez utiliser le nouveau std::stringdans le code C ++ 03, et vous pouvez utiliser l'ancien std::stringdans le code C ++ 11 (voir le lien dans le commentaire ultérieur de Matteo).
Jonathan Wakely

Réponses:

116

Quelles combinaisons de ces objets est-il et n'est-il pas sûr de se lier à un seul binaire? Pourquoi?

Pour GCC, il est sûr de lier ensemble toute combinaison d'objets A, B et C. S'ils sont tous construits avec la même version, ils sont compatibles ABI, la version standard (c'est-à-dire l' -stdoption) ne fait aucune différence.

Pourquoi? Parce que c'est une propriété importante de notre implémentation que nous nous efforçons de garantir.

Là où vous avez des problèmes, c'est si vous liez des objets compilés avec différentes versions de GCC et que vous avez utilisé des fonctionnalités instables d'un nouveau standard C ++ avant que le support de GCC pour ce standard ne soit complet. Par exemple, si vous compilez un objet en utilisant GCC 4.9 et -std=c++11et un autre objet avec GCC 5 et -std=c++11vous aurez des problèmes. Le support C ++ 11 était expérimental dans GCC 4.x, et il y avait donc des changements incompatibles entre les versions GCC 4.9 et 5 des fonctionnalités C ++ 11. De même, si vous compilez un objet avec GCC 7 et -std=c++17et un autre objet avec GCC 8 et que -std=c++17vous aurez des problèmes, car le support de C ++ 17 dans GCC 7 et 8 est encore expérimental et en évolution.

D'autre part, toute combinaison des objets suivants fonctionnera (bien que voir la note ci-dessous sur la libstdc++.soversion):

  • objet D compilé avec GCC 4.9 et -std=c++03
  • objet E compilé avec GCC 5 et -std=c++11
  • objet F compilé avec GCC 7 et -std=c++17

Cela est dû au fait que la prise en charge de C ++ 03 est stable dans les trois versions de compilateur utilisées et que les composants C ++ 03 sont donc compatibles entre tous les objets. Le support C ++ 11 est stable depuis GCC 5, mais l'objet D n'utilise aucune fonctionnalité C ++ 11, et les objets E et F utilisent tous deux des versions où le support C ++ 11 est stable. La prise en charge de C ++ 17 n'est stable dans aucune des versions de compilateur utilisées, mais seul l'objet F utilise des fonctionnalités C ++ 17 et il n'y a donc pas de problème de compatibilité avec les deux autres objets (les seules fonctionnalités qu'ils partagent proviennent de C ++ 03 ou C ++ 11, et les versions utilisées rendent ces parties OK). Si vous vouliez plus tard compiler un quatrième objet, G, en utilisant GCC 8 et que -std=c++17vous auriez besoin de recompiler F avec la même version (ou pas de lien vers F) car les symboles C ++ 17 dans F et G sont incompatibles.

La seule mise en garde pour la compatibilité décrite ci-dessus entre D, E et F est que votre programme doit utiliser la libstdc++.sobibliothèque partagée de GCC 7 (ou version ultérieure). Comme l'objet F a été compilé avec GCC 7, vous devez utiliser la bibliothèque partagée de cette version, car la compilation de n'importe quelle partie du programme avec GCC 7 pourrait introduire des dépendances sur des symboles qui ne sont pas présents dans libstdc++.soGCC 4.9 ou GCC 5. De même, si vous avez lié à l'objet G, construit avec GCC 8, vous devrez utiliser le libstdc++.sode GCC 8 pour vous assurer que tous les symboles nécessaires à G sont trouvés. La règle simple est de s'assurer que la bibliothèque partagée que le programme utilise au moment de l'exécution est au moins aussi nouvelle que la version utilisée pour compiler l'un des objets.

Une autre mise en garde lors de l'utilisation de GCC, déjà mentionnée dans les commentaires de votre question, est que depuis GCC 5, il existe deux implémentations destd::string disponibles dans libstdc ++. Les deux implémentations ne sont pas compatibles avec les liens (elles ont des noms mutilés différents, ne peuvent donc pas être liées ensemble) mais peuvent coexister dans le même binaire (elles ont des noms mutilés différents, donc ne pas entrer en conflit si un objet utilise std::stringet le autres utilisations std::__cxx11::string). Si vos objets utilisent, std::stringils doivent généralement tous être compilés avec la même implémentation de chaîne. Compilez avec -D_GLIBCXX_USE_CXX11_ABI=0pour sélectionner l' gcc4-compatibleimplémentation d' origine , ou -D_GLIBCXX_USE_CXX11_ABI=1pour sélectionner la nouvelle cxx11implémentation (ne vous laissez pas berner par le nom, il peut aussi être utilisé en C ++ 03, il s'appellecxx11car il est conforme aux exigences C ++ 11). L'implémentation par défaut dépend de la configuration de GCC, mais la valeur par défaut peut toujours être remplacée au moment de la compilation avec la macro.

Jonathan Wakely
la source
"parce que compiler n'importe quelle partie du programme avec GCC 7 pourrait introduire des dépendances sur les symboles présents dans la libstdc ++. Donc à partir de GCC 4.9 ou GCC 5" vous vouliez dire qui ne sont PAS présents à partir de GCC 4.9 ou GCC 5, non? Cela s'applique-t-il également aux liens statiques? Merci pour les informations sur la compatibilité entre les versions du compilateur.
Hadi Brais
1
Je viens de réaliser un énorme défaut en offrant une prime sur cette question. 😂
Courses de légèreté en orbite
4
@ricab Je suis sûr à 90% que la réponse est la même pour Clang / libc ++, mais je n'ai aucune idée de MSVC.
Jonathan Wakely
1
Cette réponse est excellente. Est-il documenté quelque part que 5.0+ est stable pour 11/14?
Barry
1
Pas très clairement ou en un seul endroit. gcc.gnu.org/gcc-5/changes.html#libstdcxx et gcc.gnu.org/onlinedocs/libstdc++/manual/api.html#api.rel_51 déclarent que la prise en charge de la bibliothèque pour C ++ 11 est complète (le langage le support était complet auparavant, mais toujours "expérimental"). Le support de la bibliothèque C ++ 14 est toujours répertorié comme expérimental jusqu'à la version 6.1, mais je pense qu'en pratique, rien n'a changé entre 5.x et 6.x qui affecte ABI.
Jonathan Wakely
16

Il y a deux parties à la réponse. Compatibilité au niveau du compilateur et compatibilité au niveau de l'éditeur de liens. Commençons par le premier.

supposons que tous les en-têtes ont été écrits en C ++ 11

L'utilisation du même compilateur signifie que le même en-tête de bibliothèque standard et les mêmes fichiers source (les onces associés au compilateur) seront utilisés quel que soit le standard C ++ cible. Par conséquent, les fichiers d'en-tête de la bibliothèque standard sont écrits pour être compatibles avec toutes les versions C ++ prises en charge par le compilateur.

Cela dit, si les options du compilateur utilisées pour compiler une unité de traduction spécifient une norme C ++ particulière, alors toutes les fonctionnalités qui ne sont disponibles que dans les normes plus récentes ne devraient pas être accessibles. Ceci est fait en utilisant la __cplusplusdirective. Voir le fichier source vectoriel pour un exemple intéressant de la façon dont il est utilisé. De même, le compilateur rejettera toutes les fonctionnalités syntaxiques offertes par les nouvelles versions de la norme.

Tout cela signifie que votre hypothèse ne peut s'appliquer qu'aux fichiers d'en-tête que vous avez écrits. Ces fichiers d'en-tête peuvent provoquer des incompatibilités lorsqu'ils sont inclus dans différentes unités de traduction ciblant différentes normes C ++. Ceci est discuté dans l'annexe C de la norme C ++. Il y a 4 articles, je ne parlerai que du premier et je mentionnerai brièvement le reste.

C.3.1 Clause 2: conventions lexicales

Les guillemets simples délimitent un littéral de caractère en C ++ 11, alors qu'ils sont des séparateurs de chiffres en C ++ 14 et C ++ 17. Supposons que vous ayez la définition de macro suivante dans l'un des fichiers d'en-tête C ++ 11 purs:

#define M(x, ...) __VA_ARGS__

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

Considérez deux unités de traduction qui incluent le fichier d'en-tête, mais ciblent respectivement C ++ 11 et C ++ 14. Lorsque vous ciblez C ++ 11, la virgule entre guillemets n'est pas considérée comme un séparateur de paramètres; il n'y a qu'un seul paramètre. Par conséquent, le code serait équivalent à:

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

En revanche, lorsque vous ciblez C ++ 14, les guillemets simples sont interprétés comme des séparateurs de chiffres. Par conséquent, le code serait équivalent à:

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

Le point ici est que l'utilisation de guillemets simples dans l'un des fichiers d'en-tête purs en C ++ 11 peut entraîner des bogues surprenants dans les unités de traduction qui ciblent C ++ 14/17. Par conséquent, même si un fichier d'en-tête est écrit en C ++ 11, il doit être écrit avec soin pour s'assurer qu'il est compatible avec les versions ultérieures de la norme. La __cplusplusdirective peut être utile ici.

Les trois autres clauses de la norme comprennent:

C.3.2 Article 3: concepts de base

Changement : nouveau désallocateur habituel (sans placement)

Justification : Requis pour la désallocation de taille.

Effet sur la fonctionnalité d'origine : un code C ++ 2011 valide pourrait déclarer une fonction d'allocation de placement globale et une fonction de désallocation comme suit:

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

Dans la présente Norme internationale, cependant, la déclaration de suppression d'opérateur peut correspondre à une suppression d'opérateur habituelle (sans placement) prédéfinie (3.7.4). Si c'est le cas, le programme est mal formé, comme c'était le cas pour les fonctions d'allocation des membres de classe et les fonctions de désallocation (5.3.4).

C.3.3 Article 7: déclarations

Modification : les fonctions membres non statiques constexpr ne sont pas implicitement des fonctions membres const.

Justification : nécessaire pour permettre aux fonctions membres de constexpr de muter l'objet.

Effet sur la caractéristique d'origine : Un code C ++ 2011 valide peut ne pas être compilé dans la présente Norme internationale.

Par exemple, le code suivant est valide en C ++ 2011 mais non valide dans la présente Norme internationale car il déclare la même fonction membre deux fois avec des types de retour différents:

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

C.3.4 Article 27: bibliothèque d'entrées / sorties

Changement : obtient n'est pas défini.

Justification : l'utilisation de get est considérée comme dangereuse.

Effet sur la fonctionnalité d'origine : un code C ++ 2011 valide qui utilise la fonction gets peut ne pas être compilé dans la présente Norme internationale.

Les incompatibilités potentielles entre C ++ 14 et C ++ 17 sont discutées en C.4. Étant donné que tous les fichiers d'en-tête non standard sont écrits en C ++ 11 (comme spécifié dans la question), ces problèmes ne se produiront pas, je ne les mentionnerai donc pas ici.

Je vais maintenant discuter de la compatibilité au niveau de l'éditeur de liens. En général, les raisons potentielles d'incompatibilités sont les suivantes:

Si le format du fichier objet résultant dépend du standard C ++ cible, l'éditeur de liens doit être en mesure de lier les différents fichiers objets. Dans GCC, LLVM et VC ++, ce n'est heureusement pas le cas. Autrement dit, le format des fichiers d'objets est le même quel que soit le standard cible, bien qu'il dépende fortement du compilateur lui-même. En fait, aucun des éditeurs de liens de GCC, LLVM et VC ++ n'a besoin de connaissances sur le standard C ++ cible. Cela signifie également que nous pouvons lier des fichiers objets déjà compilés (reliant statiquement le runtime).

Si la routine de démarrage du programme (la fonction qui appelle main) est différente pour différentes normes C ++ et que les différentes routines ne sont pas compatibles entre elles, il ne serait pas possible de lier les fichiers objets. Dans GCC, LLVM et VC ++, ce n'est heureusement pas le cas. De plus, la signature de la mainfonction (et les restrictions qui s'y appliquent, voir Section 3.6 de la norme) est la même dans toutes les normes C ++, donc peu importe dans quelle unité de traduction elle existe.

En général, WPO peut ne pas fonctionner correctement avec des fichiers objets compilés à l'aide de différentes normes C ++. Cela dépend exactement des étapes du compilateur qui nécessitent une connaissance de la norme cible et des étapes non et de l'impact que cela a sur les optimisations inter-procédurales qui traversent les fichiers objets. Heureusement, GCC, LLVM et VC ++ sont bien conçus et n'ont pas ce problème (pas que je sache).

Par conséquent, GCC, LLVM et VC ++ ont été conçus pour permettre la compatibilité binaire entre les différentes versions de la norme C ++. Ce n'est cependant pas vraiment une exigence de la norme elle-même.

À propos, bien que le compilateur VC ++ propose le commutateur std , qui vous permet de cibler une version particulière de la norme C ++, il ne prend pas en charge le ciblage C ++ 11. La version minimale qui peut être spécifiée est C ++ 14, qui est la valeur par défaut à partir de Visual C ++ 2013 Update 3. Vous pouvez utiliser une version plus ancienne de VC ++ pour cibler C ++ 11, mais vous devrez alors utiliser différents compilateurs VC ++ pour compiler différentes unités de traduction qui ciblent différentes versions de la norme C ++, ce qui à tout le moins casserait WPO.

CAVEAT: Ma réponse n'est peut-être pas complète ou très précise.

Hadi Brais
la source
La question visait vraiment à concerner les liens plutôt que la compilation. Je reconnais (grâce à ce commentaire ) que ce n'était peut-être pas clair et je l'ai modifié pour indiquer clairement que tous les en-têtes inclus ont la même interprétation dans les trois normes.
ricab
@ricab La réponse couvre à la fois la compilation et la liaison. Je pensais que vous posiez des questions sur les deux.
Hadi Brais
1
En effet, mais je trouve que la réponse est beaucoup trop longue et déroutante, en particulier jusqu'à ce que "Je vais maintenant discuter de la compatibilité au niveau de l'éditeur de liens". Vous pouvez remplacer tout ce qui précède par quelque chose comme si les en-têtes inclus ne peuvent pas être supposés avoir la même signification en C ++ 11 et C ++ 14/17, alors il n'est pas sûr de les inclure en premier lieu . Pour la partie restante, avez-vous une source montrant que ces trois puces sont les seules raisons potentielles d'incompatibilité? Merci pour la réponse dans tous les cas, je vote toujours
ricab
@ricab Je ne peux pas le dire avec certitude. C'est pourquoi j'ai ajouté la mise en garde à la fin de la réponse. Toute autre personne est la bienvenue pour élargir la réponse pour la rendre plus précise ou complète au cas où j'aurais manqué quelque chose.
Hadi Brais
Cela me trouble: "Utiliser le même compilateur signifie que le même en-tête de bibliothèque standard et les mêmes fichiers source (...) seront utilisés". Comment cela peut-il être le cas? Si j'ai un ancien code compilé avec gcc5, les 'fichiers du compilateur' qui appartenaient à cette version ne peuvent pas être à l'épreuve du temps. Pour le code source compilé à des moments (extrêmement) différents avec différentes versions de compilateur, nous pouvons être à peu près sûrs que l'en-tête de la bibliothèque et les fichiers source sont différents. Avec votre règle selon laquelle ceux-ci devraient être les mêmes, vous devez recompiler le code source plus ancien avec gcc5, ... et vous assurer qu'ils utilisent tous les derniers (mêmes) fichiers de compilation.
user2943111
2

Les nouvelles normes C ++ se divisent en deux parties: les fonctionnalités du langage et les composants de bibliothèque standard.

Comme vous l'entendez par nouveau standard , les changements dans la langue elle-même (par exemple à distance pour) il n'y a presque pas de problème (parfois des conflits existent dans les en-têtes de bibliothèque tiers avec des fonctionnalités de langage standard plus récentes).

Mais bibliothèque standard ...

Chaque version du compilateur est livrée avec une implémentation de la bibliothèque standard C ++ (libstdc ++ avec gcc, libc ++ avec clang, bibliothèque standard MS C ++ avec VC ++, ...) et exactement une implémentation, peu d'implémentations pour chaque version standard. Dans certains cas, vous pouvez également utiliser une autre implémentation de la bibliothèque standard que le compilateur fourni. Ce dont vous devriez vous soucier, c'est de lier une ancienne implémentation de bibliothèque standard à une plus récente.

Le conflit qui pourrait survenir entre les bibliothèques tierces et votre code est la bibliothèque standard (et d'autres bibliothèques) qui est liée à ces bibliothèques tierces.

E. Vakili
la source
"Chaque version du compilateur est livrée avec une implémentation de STL" Non, ils ne le font pas
Lightness Races in Orbit
@LightnessRacesinOrbit Voulez-vous dire qu'il n'y a pas de ralation entre par exemple libstdc ++ et gcc?
E. Vakili
8
Non, je veux dire que la STL est effectivement obsolète depuis un peu plus de vingt ans. Vous vouliez dire la bibliothèque standard C ++. En ce qui concerne le reste de la réponse, pouvez-vous fournir des références / preuves pour étayer votre demande? Je pense que pour une question comme celle-ci, c'est important.
Courses de légèreté en orbite
3
Désolé, non, ce n'est pas clair dans le texte. Vous avez fait des affirmations intéressantes, mais ne les avez pas encore étayées par des preuves.
Courses de légèreté en orbite