Cosa succede alle variabili globali e statiche in una libreria condivisa quando è collegata dinamicamente?


127

Sto cercando di capire cosa succede quando i moduli con variabili globali e statiche sono collegati dinamicamente a un'applicazione. Per moduli, intendo ogni progetto in una soluzione (lavoro molto con Visual Studio!). Questi moduli sono incorporati in * .lib o * .dll o nello stesso * .exe.

Capisco che il binario di un'applicazione contiene dati globali e statici di tutte le singole unità di traduzione (file oggetto) nel segmento di dati (e legge solo segmento di dati se const).

  • Cosa succede quando questa applicazione utilizza un modulo A con collegamento dinamico in fase di caricamento? Presumo che la DLL abbia una sezione per i suoi valori globali e statici. Il sistema operativo li carica? In caso affermativo, dove vengono caricati?

  • E cosa succede quando l'applicazione utilizza un modulo B con collegamento dinamico in fase di esecuzione?

  • Se ho due moduli nella mia applicazione che utilizzano entrambi A e B, le copie delle variabili globali A e B vengono create come indicato di seguito (se sono processi diversi)?

  • Le DLL A e B hanno accesso alle applicazioni globali?

(Si prega di indicare anche le ragioni)

Citando da MSDN :

Le variabili dichiarate come globali in un file di codice sorgente DLL vengono trattate come variabili globali dal compilatore e dal linker, ma ogni processo che carica una determinata DLL ottiene la propria copia delle variabili globali di quella DLL. L'ambito delle variabili statiche è limitato al blocco in cui vengono dichiarate le variabili statiche. Di conseguenza, ogni processo ha la propria istanza delle variabili globali e statiche della DLL per impostazione predefinita.

e da qui :

Quando si collegano dinamicamente i moduli, può non essere chiaro se le diverse librerie hanno le proprie istanze di globali o se le globali sono condivise.

Grazie.


3
Per moduli probabilmente intendi libs . C'è una proposta per aggiungere moduli allo standard C ++ con una definizione più precisa di cosa sarebbe un modulo e una semantica diversa rispetto alle normali librerie al momento.
David Rodríguez - dribeas

Ah, avrebbe dovuto chiarirlo. Considero diversi progetti in una soluzione (lavoro molto con Visual Studio) come moduli. Questi moduli sono incorporati in * .lib o * .dll.
Raja

3
@ DavidRodríguez-dribeas Il termine "modulo" è il termine tecnico corretto per i file eseguibili indipendenti (completamente collegati), inclusi: programmi eseguibili, librerie a collegamento dinamico (.dll) o oggetti condivisi (.so). È perfettamente appropriato qui, e il significato è corretto e ben compreso. Fino a quando non ci sarà una caratteristica standard chiamata "moduli", la definizione di essa rimane quella tradizionale, come ho spiegato.
Mikael Persson

Risposte:


176

Questa è una differenza piuttosto famosa tra Windows e sistemi simili a Unix.

Non importa cosa:

  • Ogni processo ha il proprio spazio degli indirizzi, il che significa che non c'è mai memoria condivisa tra i processi (a meno che non si utilizzi una libreria o estensioni di comunicazione tra processi).
  • La One Definition Rule (ODR) si applica ancora, il che significa che puoi avere solo una definizione della variabile globale visibile al momento del collegamento (collegamento statico o dinamico).

Quindi, la questione chiave qui è davvero la visibilità .

In tutti i casi, le staticvariabili globali (o funzioni) non sono mai visibili dall'esterno di un modulo (dll / so o eseguibile). Lo standard C ++ richiede che questi abbiano un collegamento interno, il che significa che non sono visibili all'esterno dell'unità di traduzione (che diventa un file oggetto) in cui sono definiti. Quindi, questo risolve il problema.

Dove diventa complicato è quando hai externvariabili globali. Qui, i sistemi Windows e Unix sono completamente diversi.

Nel caso di Windows (.exe e .dll), le externvariabili globali non fanno parte dei simboli esportati. In altre parole, moduli diversi non sono in alcun modo consapevoli delle variabili globali definite in altri moduli. Ciò significa che si ottengono errori del linker se si tenta, ad esempio, di creare un eseguibile che dovrebbe utilizzare una externvariabile definita in una DLL, perché ciò non è consentito. Dovresti fornire un file oggetto (o libreria statica) con una definizione di quella variabile esterna e collegarla staticamente sia con l'eseguibile che con la DLL, risultando in due variabili globali distinte (una appartenente all'eseguibile e una appartenente alla DLL ).

Per esportare effettivamente una variabile globale in Windows, è necessario utilizzare una sintassi simile alla sintassi della funzione di esportazione / importazione, ovvero:

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

Quando lo fai, la variabile globale viene aggiunta all'elenco dei simboli esportati e può essere collegata come tutte le altre funzioni.

Nel caso di ambienti tipo Unix (come Linux), le librerie dinamiche, chiamate "oggetti condivisi" con estensione .soesportano tutte le externvariabili (o funzioni) globali. In questo caso, se si effettua il collegamento durante il caricamento da qualsiasi luogo a un file oggetto condiviso, le variabili globali vengono condivise, ovvero collegate insieme come una sola. Fondamentalmente, i sistemi simili a Unix sono progettati per fare in modo che non vi sia praticamente alcuna differenza tra il collegamento con una libreria statica o dinamica. Anche in questo caso, ODR si applica su tutta la linea: una externvariabile globale verrà condivisa tra i moduli, il che significa che dovrebbe avere una sola definizione tra tutti i moduli caricati.

Infine, in entrambi i casi, per sistemi Windows o tipo Unix, è possibile eseguire il collegamento run-time della libreria dinamica, ovvero utilizzando LoadLibrary()/ GetProcAddress()/ FreeLibrary()o dlopen()/ dlsym()/ dlclose(). In tal caso, devi ottenere manualmente un puntatore a ciascuno dei simboli che desideri utilizzare e ciò include le variabili globali che desideri utilizzare. Per le variabili globali, è possibile utilizzare GetProcAddress()o dlsym()esattamente come si fa per le funzioni, a condizione che le variabili globali facciano parte dell'elenco di simboli esportato (secondo le regole dei paragrafi precedenti).

E, naturalmente, come nota finale necessaria: le variabili globali dovrebbero essere evitate . E credo che il testo che hai citato (riguardo a cose "poco chiare") si riferisca esattamente alle differenze specifiche della piattaforma che ho appena spiegato (le librerie dinamiche non sono realmente definite dallo standard C ++, questo è un territorio specifico della piattaforma, il che significa è molto meno affidabile / portatile).


5
Ottima risposta, grazie! Ho un seguito: poiché la DLL è un pezzo di codice e dati autonomo, ha una sezione del segmento di dati simile agli eseguibili? Sto cercando di capire dove e come questi dati vengono caricati (a) quando viene utilizzata la libreria condivisa.
Raja

18
@ Raja Sì, la DLL ha un segmento di dati. Infatti, in termini di file stessi, eseguibili e DLL sono praticamente identici, l'unica vera differenza è un flag che è impostato nell'eseguibile per dire che contiene una funzione "principale". Quando un processo carica una DLL, il suo segmento di dati viene copiato da qualche parte nello spazio degli indirizzi del processo e anche il codice di inizializzazione statico (che inizializzerebbe variabili globali non banali) viene eseguito all'interno dello spazio degli indirizzi del processo. Il caricamento è lo stesso dell'eseguibile, tranne per il fatto che lo spazio degli indirizzi del processo viene espanso invece di crearne uno nuovo.
Mikael Persson

4
Che ne dici delle variabili statiche definite all'interno di una funzione inline di una classe? ad esempio, definire "class A {void foo () {static int st_var = 0;}}" nel file di intestazione e includerlo nel modulo A e nel modulo B, A / B condividerà la stessa st_var o ognuno avrà la propria copia?
camino

2
@camino Se la classe viene esportata (ovvero definita con __attribute__((visibility("default")))), A / B condividerà la stessa st_var. Ma se la classe è definita con __attribute__((visibility("hidden"))), il modulo A e il modulo B avranno la propria copia, non condivisa.
Wei Guo

1
@camino __declspec (dllexport)
ruipacheco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.