J'essaie de comprendre ce qui se passe lorsque des modules avec des globaux et des variables 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 * .lib ou * .dll ou le * .exe lui-même.
Je comprends que le binaire d'une application contient des données globales et statiques de toutes les unités de traduction individuelles (fichiers objet) dans le segment de données (et lecture seule du segment de données si const).
Que se passe-t-il lorsque cette application utilise un module A avec une liaison dynamique au moment du chargement? Je suppose que la DLL a une section pour ses globaux et statiques. Le système d'exploitation les charge-t-il? Si oui, où sont-ils chargés?
Et que se passe-t-il lorsque l'application utilise un module B avec une liaison dynamique à l'exécution?
Si j'ai deux modules dans mon application qui utilisent à la fois A et B, des copies des globaux 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 applications globales?
(Veuillez également indiquer vos raisons)
Citant de MSDN :
Les variables déclarées comme globales dans un fichier de code source DLL sont traitées comme des variables globales par le compilateur et l'éditeur de liens, mais chaque processus qui charge une DLL donnée obtient sa propre copie des variables globales de cette 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 DLL par défaut.
et d' ici :
Lors de la liaison dynamique de modules, il peut être difficile de savoir si différentes bibliothèques ont leurs propres instances de globals ou si les globals sont partagés.
Merci.
Réponses:
C'est une différence assez célèbre entre les systèmes Windows et Unix.
Peu importe ce que:
Donc, le problème clé ici est vraiment la visibilité .
Dans tous les cas,
static
les variables globales (ou fonctions) ne sont jamais visibles de l'extérieur d'un module (dll / so ou exécutable). Le standard C ++ exige qu'ils aient un lien interne, ce qui signifie qu'ils ne sont pas visibles à l'extérieur de l'unité de traduction (qui devient un fichier objet) dans lequel ils sont définis. Donc, cela règle ce problème.Là où cela se complique, c'est lorsque vous avez
extern
des variables globales. Ici, les systèmes Windows et Unix sont complètement différents.Dans le cas de Windows (.exe et .dll), les
extern
variables globales ne font pas partie des symboles exportés. En d'autres termes, 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 l'éditeur de liens si vous essayez, par exemple, de créer un exécutable qui est censé utiliser uneextern
variable définie dans une DLL, car cela n'est pas autorisé. Vous devrez fournir un fichier objet (ou bibliothèque statique) avec une définition de cette variable extern et lier statiquement avec à la fois l'exécutable et la DLL, résultant en deux variables globales distinctes (une appartenance à l'exécutable et un appartenant à la DLL ).Pour exporter réellement une variable globale sous Windows, vous devez utiliser une syntaxe similaire à la syntaxe d'export / import de la fonction, à savoir:
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 extension,
.so
exportent toutesextern
les variables globales (ou fonctions). Dans ce cas, si vous effectuez une liaison au moment du chargement de n'importe où 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 pour faire en sorte qu'il n'y ait pratiquement aucune différence entre la liaison avec une bibliothèque statique ou dynamique. Encore une fois, l'ODR s'applique à tous les niveaux: uneextern
variable globale sera partagée entre les modules, ce qui signifie qu'elle ne devrait avoir qu'une seule définition sur tous les modules chargés.Enfin, dans les deux cas, pour les systèmes Windows ou de type Unix, vous pouvez effectuer une liaison d' exécution de la bibliothèque dynamique, c'est-à-dire en utilisant soit
LoadLibrary()
/GetProcAddress()
/FreeLibrary()
soitdlopen()
/dlsym()
/dlclose()
. Dans ce cas, vous devez obtenir manuellement un pointeur vers chacun des symboles que vous souhaitez utiliser, et qui inclut les variables globales que vous souhaitez utiliser. Pour les variables globales, vous pouvez utiliserGetProcAddress()
oudlsym()
tout simplement la même chose que pour les fonctions, à condition que les variables globales fassent partie de la liste de symboles exportée (selon les règles des paragraphes précédents).Et bien sûr, comme une note finale nécessaire: les variables globales doivent être évitées . Et je crois que le texte que vous avez cité (à propos des choses "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 le standard C ++, c'est un territoire spécifique à la plate-forme, ce qui signifie qu'il est beaucoup moins fiable / portable).
la source
__attribute__((visibility("default")))
), alors A / B partagera la même variable st_var. Mais si la classe est définie avec__attribute__((visibility("hidden")))
, alors le module A et le module B auront sa propre copie, non partagée.