C ++ 11 a-t-il résolu le problème de la transmission d'objets std lib entre les limites de bibliothèques dynamiques / partagées? (ie dlls et ainsi)?

34

L’un de mes principaux griefs à propos du C ++ est qu’il est difficile en pratique de faire passer des objets de bibliothèque std en dehors des limites de la bibliothèque dynamique (c'est-à-dire dll / so).

La bibliothèque std est souvent en-tête uniquement. Ce qui est génial pour faire des optimisations géniales. Cependant, pour les dll, elles sont souvent construites avec des paramètres de compilateur différents qui peuvent avoir un impact sur la structure / le code interne des conteneurs d'une bibliothèque std. Par exemple, dans MSVC, une dll peut être générée avec le débogage itérateur, alors qu'une autre est générée avec elle. Ces deux dll peuvent rencontrer des problèmes lors de la transmission de conteneurs std. Si j'expose std::stringdans mon interface, je ne peux pas garantir que le code utilisé par le client std::stringcorrespond exactement à celui de ma bibliothèque std::string.

Cela conduit à des problèmes difficiles à résoudre, des maux de tête, etc. Vous pouvez soit contrôler de manière rigide les paramètres du compilateur dans votre organisation pour éviter ces problèmes, soit utiliser une interface C plus simple qui ne les aura pas. Ou spécifiez à vos clients les paramètres de compilateur attendus qu'ils doivent utiliser (ce qui est inutile si une autre bibliothèque spécifie d'autres paramètres de compilateur).

Ma question est de savoir si C ++ 11 a essayé de faire quelque chose pour résoudre ces problèmes?

Doug T.
la source
3
Je ne connais pas la réponse à votre question, mais je peux dire que vos préoccupations sont partagées. C'est pourquoi je n'utiliserai pas le C ++ dans mes projets, car nous attachons une grande importance à la stabilité d'ABI au détriment de chaque cycle d'efficacité potentielle.
Donal Fellows
2
S'il vous plaît distinguer. C'est dur entre DLLs. Entre SOles deux, ça a toujours bien fonctionné.
Jan Hudec
1
Strictement parlant, ce n’est pas un problème uniquement en C ++. Il est possible d'avoir ce problème avec d'autres langues.
MrFox
2
@JanHudec Je peux vous garantir que, entre deux SO, ne fonctionne pas aussi magiquement que vous semblez l'indiquer. Étant donné la visibilité des symboles et le fonctionnement courant du nom, vous serez peut-être mieux isolé d'un problème, mais en compiler un avec des drapeaux différents, etc., et supposer que vous puissiez le lier dans un programme avec d'autres drapeaux est un sinistre.
Sdg
3
@sdg: Avec les drapeaux par défaut et la visibilité par défaut, cela fonctionne. Si vous les changez et que vous avez des problèmes, c'est votre problème et personne d'autre.
Jan Hudec

Réponses:

20

Vous avez raison de dire qu’il est préférable d’éviter tout élément STL (en fait, tout élément d’une bibliothèque tierce qui est basée sur un modèle) dans une API C ++ publique. Vous souhaitez également suivre la longue liste de règles sur http://www.ros.org/reps/rep-0009.html#definition pour empêcher la rupture ABI, ce qui fait de la programmation des API C ++ publiques une corvée.

Et la réponse concernant C ++ 11 est non, cette norme ne touche pas cela. Plus intéressant, pourquoi pas? La réponse est parce que C ++ 17 est très touchant et que, pour que les modules C ++ soient implémentés, nous avons besoin de modèles exportés pour fonctionner, et pour cela, nous avons besoin d’un compilateur de type LLVM, tel que clang, capable de transférer tout l’AST sur disque, puis Effectuez des recherches, en fonction de l'appelant, pour traiter les nombreux cas de violation de l'ODR dans tout grand projet C ++ - qui, par ailleurs, inclut beaucoup de code GCC et ELF.

Enfin, je vois beaucoup de commentaires haineux et de commentaires pro-GCC de MSVC. Celles-ci sont très mal informées - GCC sur ELF est fondamentalement, et irrémédiablement, incapable de produire du code C ++ valide et correct. Les raisons à cela sont nombreuses et légion, mais je citerai rapidement un exemple: GCC sur ELF ne peut pas produire en toute sécurité des extensions Python écrites à l'aide de Boost.Python, où plusieurs extensions basées sur Boost.Python sont chargées dans Python. En effet, ELF avec sa table de symboles C globale n’est tout simplement pas capable, par sa conception, de prévenir les violations ODR causant des segfaults, alors que PE et MachO et même la spécification proposée pour les modules C ++ utilisent tous des tables de symboles par module, ce qui signifie aussi des temps d’initialisation du processus beaucoup plus rapides. Et il y a beaucoup plus de problèmes: voir un StackOverflow auquel j'ai répondu récemment àhttps://stackoverflow.com/questions/14268736/symbol-visibility-exceptions-runtime-error/14364055#14364055 par exemple, lorsque les levées d'exceptions C ++ sont irrémédiablement brisées sur ELF.

Dernier point: en ce qui concerne l’interopérabilité de différents STL, c’est un gros problème pour de nombreuses grandes entreprises qui essaient de mélanger des bibliothèques tierces étroitement intégrées à certaines implémentations de STL. La seule solution est un nouveau mécanisme permettant au C ++ de gérer l'interopérabilité STL. Vous pouvez également réparer l'interopérabilité du compilateur pour pouvoir, par exemple, mélanger des fichiers objet compilés MSVC, GCC et Clang, et tout fonctionne. . Je regarderais les efforts de C ++ 17 et verrais ce qui se passera dans les prochaines années - je serais surpris que rien ne se produise.

Niall Douglas
la source
Bonne réponse! J'espère seulement que Clang améliorera la compatibilité de Windows et qu'il pourrait définir un bon compilateur standard par défaut. Le système d'inclusion / en-tête textuel de C ++ est horrible. Je suis impatient de voir le jour où les modules simplifieront l'organisation du code C ++, réduiront considérablement les temps de compilation et amélioreront l'interopérabilité du compilateur avec les captures violant l'ODR.
Alessandro Stamatto
3
Personnellement, je m'attends en fait à une augmentation substantielle des temps de compilation. Traverser rapidement un AST intra-module est très difficile, et nous aurons probablement besoin d'un cache de mémoire partagée en mémoire. Cependant, presque tout ce qui est mauvais devient meilleur. BTW, les fichiers d’en-tête sont définitivement conservés, les modules C ++ actuels ont des fichiers d’interface mappés 1-à-1 aux fichiers d’en-tête. De plus, les fichiers d'interface générés automatiquement seront des C ++ légaux. Ainsi, un en-tête hérité obtient simplement les macros C filtrées et crachées sous forme de fichiers d'interface. Sympa hein?
Niall Douglas
Cool! J'ai tellement de doutes sur les modules. Le système de modules prendra-t-il en compte l'inclusion textuelle ou l'inclusion symbolique? Avec la présente directive include, le compilateur doit recompiler des dizaines de milliers de lignes de code pour chaque fichier source. Le système de modules autorisera-t-il un jour le code sans déclarations de transfert? Cela améliorera-t-il / facilitera-t-il la construction d'outils?
Alessandro Stamatto
2
-1 pour suggérer que tous les modèles tiers sont suspects. La modification de la configuration est indépendante du fait que l'élément configuré soit un modèle.
DeadMG
1
@Alessandro: Les modules C ++ proposés désactivent explicitement les macros C. Vous pouvez utiliser des modèles, ou maintenant. Les interfaces proposées sont légales C ++, simplement générées automatiquement et peuvent éventuellement être précompilées pour accélérer le processus de reparation. En fait, je ne sais pas pour les deux dernières questions: ça dépend :)
Niall Douglas
8

La spécification n'a jamais eu ce problème. En effet, le concept appelé "règle de définition unique" stipule que chaque symbole a exactement une définition dans le processus en cours.

Les DLL Windows violent cette exigence. C'est pourquoi il y a tous ces problèmes. C'est donc à Microsoft de le réparer, et non au comité de normalisation C ++. Unix n’a jamais eu ce problème, car les bibliothèques partagées fonctionnent différemment ici et sont conformes par défaut à une règle de définition (vous pouvez explicitement le casser, mais vous ne le faites évidemment que si vous savez que vous pouvez vous le permettre et que vous devez extraire les quelques cycles supplémentaires).

Les DLL Windows violent une règle de définition pour les raisons suivantes:

  • Ils codent en dur à partir de quelle bibliothèque dynamique un symbole sera utilisé pendant le temps de liaison statique et résolvent les symboles de manière statique dans la bibliothèque qui les définit. Ainsi, si le même symbole faible est généré dans plusieurs bibliothèques partagées et ces bibliothèques que celles utilisées dans un processus unique, l'éditeur de liens dynamique n'a aucune chance de fusionner ces symboles. Généralement, ces symboles sont des membres statiques ou des impédimentés de classe d'instances de modèles, ce qui pose des problèmes lors du passage d'instances entre du code dans différentes DLL.
  • Ils indiquent si le symbole sera importé de la bibliothèque dynamique déjà pendant la compilation. Ainsi, le code lié statiquement à certaines bibliothèques est incompatible avec le code lié dynamiquement à la même bibliothèque.

Unix utilisant les exportations au format ELF importe implicitement tous les symboles exportés pour éviter le premier problème et ne fait pas la distinction entre les symboles résolus statiquement et dynamiquement avant le temps de liaison statique pour éviter le second.


L'autre problème concerne les drapeaux du compilateur. Ce problème existe pour tout programme composé de plusieurs unités de compilation, les bibliothèques dynamiques ne doivent pas être impliquées. Cependant, c'est bien pire sous Windows. Sous Unix, peu importe que vous liez statiquement ou dynamiquement, personne ne lie le runtime standard de toute façon (sous Linux, cela peut même être illégal) et il n’existe aucun runtime de débogage spécial, une construction est donc suffisante. Mais la manière dont Microsoft a implémenté les liaisons statiques et dynamiques, les applications de débogage et d’exécution ainsi que certaines autres options a provoqué une explosion combinatoire des variantes de librairies nécessaires. Là encore, problème de plate-forme plutôt que de langage C ++.

Jan Hudec
la source
2
@DougT .: GCC n'a rien à voir avec cela. La plateforme ABI a. Dans ELF, le format d’objet utilisé par la plupart des bibliothèques Unices, partagées, exporte tous les symboles visibles et importe tous les symboles qu’elles exportent. Donc, si quelque chose est généré dans plusieurs bibliothèques, l'éditeur de liens dynamique utilisera la première définition pour tous. Simple, élégant et fonctionnel.
Jan Hudec
1
@MartinBa: Il n'y a rien à fusionner, mais cela n'a pas d'importance tant que c'est identique et qu'il n'est pas supposé être fusionné en premier lieu. Oui, si vous utilisez des paramètres de compilateur incompatibles sur une plate-forme ELF, vous obtenez le même désastre que partout. Même si vous n'utilisez pas de bibliothèques partagées, c'est un peu hors sujet ici.
Jan Hudec
1
@ Jan - c'est pertinent pour votre réponse. Vous écrivez: "... une règle de définition ... les DLL Windows enfreignent cette exigence ... les bibliothèques partagées fonctionnent différemment [sous UNix] ...", mais la question posée concerne les problèmes liés aux éléments std-lib (définis dans les en-têtes). et la raison pour laquelle il n'y a pas de problème sous Unix n'a rien à voir avec SO vs DLL mais avec le fait que sous Unix (apparemment) il n'y a qu'une seule version compatible de la bibliothèque standard alors que sous Windows MS choisissait d'avoir des versions incompatibles (debug) (avec vérification prolongée etc.).
Martin Ba
1
@MartinBa: Non, la principale raison du problème sous Windows est que le mécanisme d'exportation / importation utilisé sous Windows ne peut pas fusionner correctement les membres statiques et les impedimenta de classe des classes de modèle dans tous les cas et ne peut pas fusionner des symboles liés statiquement et dynamiquement. Cela est encore pire par les multiples variantes de bibliothèque, mais le problème principal est que C ++ a besoin de la flexibilité de l'éditeur de liens que l'éditeur de liens dynamique de Windows n'a pas.
Jan Hudec
4
Je pense que cela implique que la spécification de la DLL est brisée et que la demande correspondante de «réparer» par Msft est mal placée. Le fait que les DLL ne prennent pas en charge certaines fonctionnalités de C ++ n'est pas un défaut de la spécification de DLL. Les DLL sont un mécanisme d'empaquetage indépendant du langage et du fournisseur et une ABI permettant d'exposer les points d'entrée au code machine ("appels de fonction") et aux blobs de données. Ils n’ont jamais eu l’intention de prendre en charge de manière native les fonctionnalités avancées d’une langue donnée. Ce n'est pas la faute de Msft, ou de la spécification de la DLL, que certaines personnes veulent que ce soit autre chose.
Euro Micelli
6

Non.

Il y a beaucoup de travail en cours pour remplacer le système d'en-tête, une fonctionnalité appelée Modules et qui pourrait avoir un impact sur cela, mais certainement pas une grosse.

Klaim
la source
2
Je ne pense pas que le système d'en-tête aurait un impact sur cela. Les problèmes sont que les DLL Windows violent une règle de définition (ce qui signifie qu'elles ne suivent pas les spécifications C ++, de sorte qu'un comité C ++ ne peut rien y faire) et qu'il existe de nombreuses variantes du moteur d'exécution standard dans Windows, qu'un comité C ++ peut " t faire quelque chose à propos de soit.
Jan Hudec
1
Non, ils ne le font pas. Comment pourraient-ils, la spécification ne mentionne même pas quelque chose de ce genre. En dehors de cela, lorsqu'un programme (Windows) est lié à des DLL Windows, l'ODR est satisfait: tous les symboles visibles (exportés) doivent obéir à l'ODR.
Paul Michalik
@PaulMichalik C ++ couvre la liaison (phase 9) et il me semble qu'au moins la liaison au moment du chargement des DLL / SO tombe dans la phase 9. Cela signifie que les symboles avec une liaison externe (exportée ou non) doivent être liés et se conformer à l'ODR. La liaison dynamique avec LoadLibrary / dlopen ne relève évidemment pas de ces exigences.
Bames53
@ bames53: à mon humble avis, les spécifications sont beaucoup trop faibles pour permettre des déclarations de ce type. Un fichier .dll / .so peut être considéré comme un "programme" en soi. Alors, les règles étaient satisfaites. Quelque chose comme charger d'autres "programmes" au moment de l'exécution est tellement sous-estimé par la norme que toute déclaration à ce sujet est assez arbitraire.
Paul Michalik
@PaulMichalik Si un exécutable nécessite une liaison au moment du chargement, certaines entités externes restent non résolues et les informations nécessaires à son exécution sont manquantes. LoadLibrary et dlopen ne font pas partie des spécifications, mais la liaison du temps de chargement doit clairement faire partie de la phase 9.
bames53