web-dev-qa-db-fra.com

Est-il prudent de lier des objets C ++ 17, C ++ 14 et C ++ 11

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 des raisons de simplicité, supposons que tous les en-têtes aient été écrits en C++ 11, en utilisant uniquement des constructions dont la sémantique n'a pas changé entre les trois versions standard, de sorte que les interdépendances ont été correctement exprimées avec l'inclusion d'en-tête et le compilateur. n'a pas d'objection.

De quelles combinaisons s'agit-il et n'est-il pas prudent de se lier en un seul binaire? Pourquoi?


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

63
ricab

De quelles combinaisons s'agit-il et n'est-il pas prudent de se lier en un seul binaire? Pourquoi?

Pour GCC il est prudent 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 le -std _ option) ne fait aucune différence.

Pourquoi? Parce que c'est une propriété importante de notre implémentation pour laquelle nous travaillons dur.

Vous avez des problèmes si vous liez des objets compilés avec différentes versions de GCC et vous avez utilisé des fonctionnalités instables d'un nouveau standard C++ avant la prise en charge complète de ce standard par GCC. Par exemple, si vous compilez un objet avec GCC 4.9 et -std=c++11 et un autre objet avec GCC 5 et -std=c++11 vous aurez des problèmes. La prise en charge de C++ 11 étant expérimentale dans GCC 4.x, il y avait donc des modifications incompatibles entre les versions GCC 4.9 et 5 des fonctionnalités de C++ 11. De même, si vous compilez un objet avec GCC 7 et -std=c++17 et un autre objet avec GCC 8 et -std=c++17 vous aurez des problèmes, car la prise en charge de C++ 17 dans GCC 7 et 8 est toujours expérimentale et en évolution.

Par contre, toute combinaison des objets suivants fonctionnera (bien que la note ci-dessous à propos de libstdc++.so version):

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

En effet, la prise en charge de C++ 03 est stable dans les trois versions du compilateur utilisées. Par conséquent, les composants C++ 03 sont compatibles entre tous les objets. La prise en charge de C++ 11 est stable depuis GCC 5, mais l'objet D n'utilise aucune fonctionnalité de C++ 11 et les objets E et F utilisent des versions dans lesquelles la prise en charge de 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 les fonctionnalités de C++ 17; il n’ya 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 voulez par la suite compiler un quatrième objet, G, en utilisant GCC 8 et -std=c++17 vous devrez alors recompiler F avec la même version (ou ne pas associer à F) car les symboles C++ 17 en F et G sont incompatibles.

Le seul inconvénient concernant la compatibilité décrite ci-dessus entre D, E et F est que votre programme doit utiliser le libstdc++.so bibliothèque partagée de GCC 7 (ou ultérieure). L'objet F ayant été compilé avec GCC 7, vous devez utiliser la bibliothèque partagée de cette version, car la compilation de toute partie du programme avec GCC 7 pourrait introduire des dépendances sur des symboles qui ne sont pas présents dans le fichier libstdc++.so de GCC 4.9 ou de GCC 5. De même, si vous vous associez à l’objet G construit avec GCC 8, vous devrez utiliser le libstdc++.so de GCC 8 pour s’assurer que tous les symboles nécessaires à G sont retrouvés. La règle simple est de s'assurer que la bibliothèque partagée utilisée par le programme 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 de std::string _ disponible dans libstdc ++. Les deux implémentations ne sont pas compatibles avec les liaisons (elles ont des noms différents mutilés, elles ne peuvent donc pas être liées ensemble), mais peuvent coexister dans le même fichier binaire (elles ont des noms mutilés différents, évitez donc les conflits si un objet utilise std::string et les autres utilisent std::__cxx11::string). Si vos objets utilisent std::string alors, généralement, ils devraient tous être compilés avec la même implémentation de chaîne. Compiler avec -D_GLIBCXX_USE_CXX11_ABI=0 pour sélectionner l'original gcc4-compatible _ implémentation, ou -D_GLIBCXX_USE_CXX11_ABI=1 pour sélectionner le nouveau cxx11 implémentation (ne vous laissez pas berner par son nom, il peut également être utilisé en C++ 03, il est appelé cxx11 car il est conforme aux exigences de C++ 11). L'implémentation par défaut dépend de la configuration de GCC, mais celle-ci peut toujours être remplacée lors de la compilation avec la macro.

72
Jonathan Wakely

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 l'ancien.

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ées au compilateur) seront utilisés indépendamment de la norme 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, les fonctionnalités disponibles uniquement dans les normes plus récentes ne doivent pas être accessibles. Ceci est fait en utilisant le __cplusplus directive. Voir le fichier source vector pour un exemple intéressant d'utilisation. De même, le compilateur rejettera les fonctionnalités syntaxiques offertes par les versions les plus récentes 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 entraîner 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 discuterai que du premier et mentionnerai brièvement le reste.

C.3.1 Article 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 la définition de macro suivante figure 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érons 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 dans les guillemets n'est pas considérée comme un séparateur de paramètre. il n'y a qu'une seule 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 C++ 11 purs 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, vous devez l'écrire avec soin pour vous assurer qu'il est compatible avec les versions ultérieures de la norme. Le __cplusplus directive peut être utile ici.

Les trois autres clauses de la norme incluent:

C.3.2 Article 3: concepts de base

Modification : Nouveau désallocateur usuel (non placement)

Justification : Obligatoire pour la désallocation dimensionnée.

Effet sur la fonctionnalité d'origine : du code C++ 2011 valide pourrait déclarer une fonction globale d'allocation d'emplacement et une fonction de désallocation:

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

Toutefois, dans la présente Norme internationale, 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 de membre de classe et les fonctions de désallocation (5.3.4).

C.3.3 Article 7: déclarations

Change : les fonctions membres non statiques de 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 fonctionnalité d'origine : La compilation d'un code C++ 2011 valide peut échouer 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 deux fois la même fonction membre 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

Changer : gets n'est pas défini.

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

Effet sur la fonctionnalité d'origine : La compilation d'un code C++ 2011 valide qui utilise la fonction get peut échouer 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 et 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 incompatibilités sont les suivantes:

Si le format du fichier objet résultant dépend de la norme C++ cible, l'éditeur de liens doit pouvoir lier les différents fichiers objet. 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, même s’il dépend fortement du compilateur lui-même. En fait, aucun des lieurs de GCC, LLVM et VC++ n’a besoin de connaître le standard C++ cible. Cela signifie également que nous pouvons lier des fichiers objet déjà compilés (en 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 si les différentes routines ne sont pas compatibles, 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 fonction main (et les restrictions qui s’y appliquent, voir la section 3.6 de la norme) est la même dans toutes les normes C++. Par conséquent, peu importe l’unité de traduction dans laquelle elle existe. .

En général, WPO peut ne pas fonctionner correctement avec des fichiers objet compilés en utilisant différentes normes C++. Cela dépend exactement des étapes du compilateur qui nécessitent une connaissance du standard cible et de celles qui n'en nécessitent pas, ainsi que de l'impact qu'il a sur les optimisations inter-procédurales impliquant des fichiers objet. Heureusement, GCC, LLVM et VC++ sont bien conçus et ne présentent pas ce problème (pas à ma connaissance).

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

En passant, bien que le compilateur VC++ propose le commutateur std , qui vous permet de cibler une version particulière du standard C++, il ne prend pas en charge le ciblage C++ 11. La version minimale pouvant être spécifiée est C++ 14, qui est la version par défaut à partir de Visual C++ 2013 Update 3. Vous pouvez utiliser une version antérieure 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, perturberait WPO.

CAVEAT: Ma réponse peut ne pas être complète ou très précise.

11
Hadi Brais

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

Comme vous voulez dire par nouvelle norme, les changements de langue (par exemple, à distance) ne posent pratiquement aucun problème (il existe parfois des conflits dans les en-têtes de bibliothèques tierces avec de nouvelles fonctionnalités de langage standard).

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, une bibliothèque standard MS C++ avec VC++, ...) et avec exactement une implémentation, peu d'implémentations pour chaque version standard. De plus, dans certains cas, vous pouvez utiliser une implémentation de la bibliothèque standard autre que celle fournie par le compilateur. Il est important de lier une ancienne implémentation de bibliothèque standard à une nouvelle.

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

2
E. Vakili