193 votes

Que se passe-t-il pour les variables globales et statiques dans une bibliothèque partagée lorsqu'elle est liée dynamiquement?

Je suis en train de chercher à comprendre ce qu'il se passe lorsque des modules avec des variables globales et statiques sont liés dynamiquement à une application. Par modules, j'entends chaque projet dans une solution (je travaille beaucoup avec visual studio!). Ces modules sont soit intégrés dans des fichiers *.lib ou *.dll, soit directement dans le *.exe lui-même.

Je comprends que le binaire d'une application contient les données globales et statiques de toutes les unités de translation individuelles (fichiers objet) dans le segment de données (et le segment de données en lecture seule s'il s'agit de const).

  • Que se passe-t-il lorsque cette application utilise un module A avec un lien dynamique au chargement? Je suppose que le DLL a une section pour ses variables globales et statiques. L'OS les charge-t-il? Si oui, où sont-elles chargées?

  • Et que se passe-t-il lorsque l'application utilise un module B avec un lien dynamique à l'exécution?

  • Si j'ai deux modules dans mon application qui utilisent à la fois A et B, des copies des variables globales de A et B sont-elles créées comme mentionné ci-dessous (s'il s'agit de processus différents)?

  • Les DLL A et B ont-elles accès aux variables globales de l'application?

(Veuillez également indiquer vos raisons)

Citant de MSDN:

Les variables qui sont déclarées comme globales dans un fichier source DLL sont traitées comme des variables globales par le compilateur et le lieur, mais chaque processus qui charge un DLL donné obtient sa propre copie des variables globales de ce DLL. La portée des variables statiques est limitée au bloc dans lequel les variables statiques sont déclarées. Par conséquent, chaque processus a sa propre instance des variables globales et statiques du DLL par défaut.

et de ici:

Lors de la liaison dynamique des modules, il peut être peu clair si différentes bibliothèques ont leurs propres instances de variables globales ou si les variables globales sont partagées.

Merci.

231voto

Mikael Persson Points 7174

C'est une différence assez connue entre les systèmes Windows et les systèmes de type Unix.

Peu importe :

  • Chaque processus a son propre espace d'adressage, ce qui signifie qu'il n'y a jamais de mémoire partagée entre les processus (à moins d'utiliser une bibliothèque de communication inter-processus ou des extensions).
  • La Règle de la Définition Unique (ODR) s'applique toujours, ce qui signifie que vous ne pouvez avoir qu'une seule définition de la variable globale visible au moment du lien (liaison statique ou dynamique).

Donc, la question clé ici est vraiment la visibilité.

Dans tous les cas, les variables globales static (ou fonctions) ne sont jamais visibles depuis l'extérieur d'un module (dll/so ou exécutable). La norme C++ exige que celles-ci aient une liaison interne, ce qui signifie qu'elles ne sont pas visibles en dehors de l'unité de traduction (qui devient un fichier objet) dans laquelle elles sont définies. Donc, cela règle cette question.

Les choses se compliquent lorsque vous avez des variables globales extern. Ici, les systèmes Windows et de type Unix sont complètement différents.

Dans le cas de Windows (.exe et .dll), les variables globales extern ne font pas partie des symboles exportés. En d'autres termes, les différents modules ne sont en aucun cas conscients des variables globales définies dans d'autres modules. Cela signifie que vous obtiendrez des erreurs de lien si vous essayez, par exemple, de créer un exécutable censé utiliser une variable extern définie dans une DLL, car cela n'est pas autorisé. Vous devriez fournir un fichier objet (ou une bibliothèque statique) avec une définition de cette variable externe et la lier statiquement avec à la fois l'exécutable et la DLL, ce qui se traduit par deux variables globales distinctes (une appartenant à l'exécutable et une appartenant à la DLL).

Pour exporter réellement une variable globale sous Windows, vous devez utiliser une syntaxe similaire à la syntaxe d'exportation/importation de fonction, c'est-à-dire :

#ifdef COMPILING_THE_DLL
#define MY_DLL_EXPORT extern "C" __declspec(dllexport)
#else
#define MY_DLL_EXPORT extern "C" __declspec(dllimport)
#endif

MY_DLL_EXPORT int my_global;

Lorsque vous faites cela, la variable globale est ajoutée à la liste des symboles exportés et peut être liée comme toutes les autres fonctions.

Dans le cas des environnements de type Unix (comme Linux), les bibliothèques dynamiques, appelées "objets partagés" avec l'extension .so, exportent toutes les variables globales extern (ou fonctions). Dans ce cas, si vous faites un lien au moment du chargement à partir de n'importe quel endroit vers un fichier objet partagé, alors les variables globales sont partagées, c'est-à-dire, liées ensemble comme une seule. Fondamentalement, les systèmes de type Unix sont conçus de manière à ce qu'il n'y ait pratiquement aucune différence entre le lien avec une bibliothèque statique ou dynamique. Encore une fois, la règle de la Définition Unique s'applique de manière générale : une variable globale extern sera partagée entre les modules, ce qui signifie qu'elle ne devrait avoir qu'une seule définition à travers tous les modules chargés.

Enfin, dans les deux cas, pour Windows ou les systèmes de type Unix, vous pouvez effectuer un lien au moment de l'exécution de la bibliothèque dynamique, c'est-à-dire en utilisant soit LoadLibrary()/GetProcAddress()/FreeLibrary() ou dlopen()/dlsym()/dlclose(). Dans ce cas, vous devez obtenir manuellement un pointeur vers chacun des symboles que vous souhaitez utiliser, et cela inclut les variables globales que vous souhaitez utiliser. Pour les variables globales, vous pouvez utiliser GetProcAddress() ou dlsym() de la même manière que vous le faites pour les fonctions, à condition que les variables globales fassent partie de la liste des symboles exportés (selon les règles des paragraphes précédents).

Et bien sûr, en tant que note finale nécessaire : les variables globales doivent être évitées. Et je crois que le texte que vous avez cité (à propos des choses étant "peu claires") fait référence exactement aux différences spécifiques à la plate-forme que je viens d'expliquer (les bibliothèques dynamiques ne sont pas vraiment définies par la norme C++, c'est un territoire spécifique à la plateforme, ce qui signifie que c'est beaucoup moins fiable/portable).

22voto

Deckard 5 Pegasus Points 320

La réponse laissée par Mikael Persson, bien que très détaillée, contient une erreur grave (ou du moins trompeuse), en ce qui concerne les variables globales, qui doit être clarifiée. La question initiale demandait s'il y avait des copies séparées des variables globales ou si les variables globales étaient partagées entre les processus.

La vraie réponse est la suivante: Il y a des copies séparées (multiples) des variables globales pour chaque processus, et elles ne sont pas partagées entre les processus. Ainsi, en affirmant que la Règle d'une Définition (ODR) s'applique est également très trompeur, elle ne s'applique pas dans le sens où elles ne sont PAS les mêmes globales utilisées par chaque processus, donc en réalité ce n'est pas une "Définition Unique" entre les processus.

Même si les variables globales ne sont pas "visibles" par le processus, elles sont toujours facilement "accessibles" par le processus, car toute fonction pourrait facilement renvoyer une valeur d'une variable globale au processus, ou bien, un processus pourrait définir une valeur d'une variable globale à travers un appel de fonction. Ainsi, cette réponse est également trompeuse.

En réalité, "oui" les processus ont un accès complet aux variables globales, au moins à travers les appels de fonctions à la bibliothèque. Mais pour réitérer, chaque processus a sa propre copie des variables globales, donc ce ne seront pas les mêmes globales qu'un autre processus utilise.

Ainsi, toute la réponse concernant l'exportation externe des globales est vraiment hors sujet, inutile et même pas liée à la question initiale. Parce que les globales n'ont pas besoin d'être déclarées extern pour être accédées, elles peuvent toujours être accédées indirectement par des appels de fonction à la bibliothèque.

La seule partie qui est partagée entre les processus, bien sûr, est le "code" réel. Le code est chargé uniquement à un endroit en mémoire physique (RAM), mais cette même position en mémoire physique est bien sûr cartographiée dans les emplacements de mémoire virtuelle "locaux" de chaque processus.

À l'inverse, une bibliothèque statique a une copie du code pour chaque processus déjà intégrée dans l'exécutable (ELF, PE, etc.), et bien sûr, comme les bibliothèques dynamiques, a des variables globales séparées pour chaque processus.

1voto

choppe Points 463

Dans les systèmes Unix :

Il est à noter que le linker ne se plaint pas si deux bibliothèques dynamiques exportent les mêmes variables globales. mais lors de l'exécution, une erreur de segmentation pourrait survenir en fonction des violations d'accès. Un nombre habituel présentant ce comportement serait le segmentation fault 15

segfault at xxxxxx ip xxxxxx sp xxxxxxx error 15 in a.out

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X