Was passiert mit globalen und statischen Variablen in einer gemeinsam genutzten Bibliothek, wenn diese dynamisch verknüpft ist?

127

Ich versuche zu verstehen, was passiert, wenn Module mit globalen und statischen Variablen dynamisch mit einer Anwendung verknüpft werden. Mit Modulen meine ich jedes Projekt in einer Lösung (ich arbeite viel mit Visual Studio!). Diese Module sind entweder in * .lib oder * .dll oder in die * .exe selbst integriert.

Ich verstehe, dass die Binärdatei einer Anwendung globale und statische Daten aller einzelnen Übersetzungseinheiten (Objektdateien) im Datensegment enthält (und schreibgeschütztes Datensegment, wenn const).

  • Was passiert, wenn diese Anwendung ein Modul A mit dynamischer Ladezeitverknüpfung verwendet? Ich gehe davon aus, dass die DLL einen Abschnitt für ihre globalen und statischen Elemente hat. Lädt das Betriebssystem sie? Wenn ja, wohin werden sie geladen?

  • Und was passiert, wenn die Anwendung ein Modul B mit dynamischer Laufzeitverknüpfung verwendet?

  • Wenn ich zwei Module in meiner Anwendung habe, die beide A und B verwenden, werden Kopien der Globals von A und B wie unten erwähnt erstellt (wenn es sich um unterschiedliche Prozesse handelt)?

  • Erhalten die DLLs A und B Zugriff auf die globalen Anwendungen?

(Bitte geben Sie auch Ihre Gründe an)

Zitat aus MSDN :

Variablen, die in einer DLL-Quellcodedatei als global deklariert sind, werden vom Compiler und Linker als globale Variablen behandelt, aber jeder Prozess, der eine bestimmte DLL lädt, erhält eine eigene Kopie der globalen Variablen dieser DLL. Der Umfang statischer Variablen ist auf den Block beschränkt, in dem die statischen Variablen deklariert sind. Infolgedessen verfügt jeder Prozess standardmäßig über eine eigene Instanz der globalen und statischen DLL-Variablen.

und von hier :

Bei der dynamischen Verknüpfung von Modulen kann unklar sein, ob verschiedene Bibliotheken ihre eigenen Instanzen von Globals haben oder ob die Globals gemeinsam genutzt werden.

Vielen Dank.

Raja
quelle
3
Mit Modulen meinen Sie wahrscheinlich libs . Es wird vorgeschlagen, dem C ++ - Standard Module hinzuzufügen , die eine genauere Definition des Moduls und eine andere Semantik als reguläre Bibliotheken enthalten.
David Rodríguez - Dribeas
Ah, hätte das klarstellen sollen. Ich betrachte verschiedene Projekte in einer Lösung (ich arbeite viel mit Visual Studio) als Module. Diese Module sind in * .lib oder * .dll integriert.
Raja
3
@ DavidRodríguez-dribeas Der Begriff "Modul" ist der korrekte Fachbegriff für eigenständige (vollständig verknüpfte) ausführbare Dateien, einschließlich: ausführbarer Programme, Dynamic Link-Bibliotheken (.dll) oder gemeinsam genutzter Objekte (.so). Es ist hier vollkommen angemessen, und die Bedeutung ist richtig und gut verstanden. Bis es eine Standardfunktion mit dem Namen "Module" gibt, bleibt die Definition, wie ich erklärt habe, die traditionelle.
Mikael Persson

Antworten:

176

Dies ist ein ziemlich berühmter Unterschied zwischen Windows- und Unix-ähnlichen Systemen.

Egal was:

  • Jeder Prozess verfügt über einen eigenen Adressraum. Dies bedeutet, dass zwischen den Prozessen niemals Speicher gemeinsam genutzt wird (es sei denn, Sie verwenden eine prozessübergreifende Kommunikationsbibliothek oder -erweiterungen).
  • Die One Definition Rule (ODR) gilt weiterhin, dh Sie können nur eine Definition der globalen Variablen zum Zeitpunkt der Verknüpfung anzeigen (statische oder dynamische Verknüpfung).

Das Hauptproblem hier ist also die Sichtbarkeit .

In allen Fällen sind staticglobale Variablen (oder Funktionen) niemals von außerhalb eines Moduls (dll / so oder ausführbar) sichtbar. Der C ++ - Standard verlangt, dass diese über eine interne Verknüpfung verfügen, was bedeutet, dass sie außerhalb der Übersetzungseinheit (die zu einer Objektdatei wird), in der sie definiert sind, nicht sichtbar sind. Damit ist das Problem gelöst.

Kompliziert wird es, wenn Sie externglobale Variablen haben. Hier sind Windows- und Unix-ähnliche Systeme völlig unterschiedlich.

Bei Windows (.exe und .dll) sind die externglobalen Variablen nicht Teil der exportierten Symbole. Mit anderen Worten, verschiedene Module kennen in keiner Weise globale Variablen, die in anderen Modulen definiert sind. Dies bedeutet, dass Sie Linkerfehler erhalten, wenn Sie beispielsweise versuchen, eine ausführbare Datei zu erstellen, die eine externin einer DLL definierte Variable verwenden soll , da dies nicht zulässig ist. Sie müßten eine Objektdatei (oder statische Bibliothek) mit einer Definition dieser externen Variablen bieten und sie verknüpfen statisch mit sowohl der ausführbaren Datei und den DLL, in zwei verschiedenen globalen Variablen resultierende (eine Zugehörigkeit zu der ausführbaren Datei und einem an den DLL gehör ).

Um eine globale Variable in Windows tatsächlich zu exportieren, müssen Sie eine Syntax verwenden, die der Funktion Export / Import-Syntax ähnelt, dh:

#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;

Wenn Sie dies tun, wird die globale Variable zur Liste der exportierten Symbole hinzugefügt und kann wie alle anderen Funktionen verknüpft werden.

In Unix-ähnlichen Umgebungen (wie Linux) .soexportieren die dynamischen Bibliotheken, die als "gemeinsam genutzte Objekte" mit der Erweiterung bezeichnet werden, alle externglobalen Variablen (oder Funktionen). In diesem Fall werden die globalen Variablen gemeinsam genutzt, dh wenn sie während der Ladezeit von einer beliebigen Stelle aus mit einer gemeinsam genutzten Objektdatei verknüpft werden. Grundsätzlich sind Unix-ähnliche Systeme so konzipiert, dass es praktisch keinen Unterschied zwischen der Verknüpfung mit einer statischen oder einer dynamischen Bibliothek gibt. Auch hier gilt ODR auf der ganzen Linie: Eine externglobale Variable wird von allen Modulen gemeinsam genutzt, was bedeutet, dass sie nur eine Definition für alle geladenen Module haben sollte.

In beiden Fällen können Sie für Windows- oder Unix-ähnliche Systeme die dynamische Bibliothek zur Laufzeit verknüpfen, dh entweder mit LoadLibrary()/ GetProcAddress()/ FreeLibrary()oder dlopen()/ dlsym()/ dlclose(). In diesem Fall müssen Sie manuell einen Zeiger auf jedes der Symbole erhalten, die Sie verwenden möchten, und dies schließt die globalen Variablen ein, die Sie verwenden möchten. Für globale Variablen können Sie Funktionen verwenden GetProcAddress()oder dlsym()genauso verwenden wie für Funktionen, vorausgesetzt, die globalen Variablen sind Teil der exportierten Symbolliste (gemäß den Regeln der vorherigen Absätze).

Und natürlich als notwendige letzte Anmerkung: Globale Variablen sollten vermieden werden . Und ich glaube, dass der von Ihnen zitierte Text (über "unklare" Dinge) genau auf die plattformspezifischen Unterschiede verweist, die ich gerade erklärt habe (dynamische Bibliotheken sind nicht wirklich durch den C ++ - Standard definiert, dies ist plattformspezifisches Gebiet, was bedeutet ist viel weniger zuverlässig / tragbar).

Mikael Persson
quelle
5
Tolle Antwort, danke! Ich habe ein Follow-up: Da die DLL ein in sich geschlossenes Stück Code & Daten ist, hat sie einen Datensegmentabschnitt, der ausführbaren Dateien ähnelt? Ich versuche zu verstehen, wo und wie diese Daten geladen werden, wenn die gemeinsam genutzte Bibliothek verwendet wird.
Raja
18
@ Raja Ja, die DLL hat ein Datensegment. In Bezug auf die Dateien selbst sind ausführbare Dateien und DLLs praktisch identisch. Der einzige wirkliche Unterschied besteht darin, dass in der ausführbaren Datei ein Flag gesetzt ist, das besagt, dass es eine "Haupt" -Funktion enthält. Wenn ein Prozess eine DLL lädt, wird sein Datensegment irgendwo in den Adressraum des Prozesses kopiert, und der statische Initialisierungscode (der nicht triviale globale Variablen initialisieren würde) wird auch im Adressraum des Prozesses ausgeführt. Das Laden ist das gleiche wie für die ausführbare Datei, außer dass der Prozessadressraum erweitert wird, anstatt einen neuen zu erstellen.
Mikael Persson
4
Wie wäre es mit den statischen Variablen, die in einer Inline-Funktion einer Klasse definiert sind? zB "Klasse A {void foo () {static int st_var = 0;}}" in der Header-Datei definieren und in Modul A und Modul B aufnehmen. Wird A / B dieselbe st_var gemeinsam nutzen oder hat jede eine eigene Kopie?
Camino
2
@camino Wenn die Klasse exportiert wird (dh mit definiert ist __attribute__((visibility("default")))), teilt A / B dieselbe st_var. Wenn die Klasse jedoch mit definiert ist __attribute__((visibility("hidden"))), haben Modul A und Modul B eine eigene Kopie, die nicht gemeinsam genutzt wird.
Wei Guo
1
@ Camino __declspec (dllexport)
Ruipacheco