O que acontece com variables ​​globais e estáticas em uma biblioteca compartilhada quando está dinamicamente vinculado?

Estou tentando entender o que acontece quando módulos com variables ​​globais e estáticas são dinamicamente vinculados a um aplicativo. Por módulos, quero dizer cada projeto em uma solução (eu trabalho muito com o visual studio!). Estes módulos são construídos em * .lib ou * .dll ou no próprio * .exe.

Eu entendo que o binário de um aplicativo contém dados globais e estáticos de todas as unidades individuais de tradução (arquivos de objects) no segmento de dados (e leia somente o segmento de dados, se const).

  • O que acontece quando este aplicativo usa um módulo A com vinculação dinâmica de tempo de carregamento? Eu suponho que a DLL tenha uma seção para seus globais e estáticos. O sistema operacional os carrega? Se sim, onde eles são carregados?

  • E o que acontece quando o aplicativo usa um módulo B com vinculação dinâmica em tempo de execução?

  • Se eu tiver dois módulos em meu aplicativo que usam A e B, as cópias dos globals de A e B serão criadas conforme mencionado abaixo (se forem processos diferentes)?

  • As DLLs A e B obtêm access aos aplicativos globais?

(Por favor, indique suas razões também)

Citando do MSDN :

Variáveis ​​que são declaradas como globais em um arquivo de código-fonte DLL são tratadas como variables ​​globais pelo compilador e vinculador, mas cada processo que carrega uma determinada DLL obtém sua própria cópia das variables ​​globais da DLL. O escopo das variables ​​estáticas é limitado ao bloco no qual as variables ​​estáticas são declaradas. Como resultado, cada processo tem sua própria instância das variables ​​globais e estáticas da DLL por padrão.

e daqui :

Ao vincular módulos dinamicamente, pode não estar claro se bibliotecas diferentes têm suas próprias instâncias de globais ou se as globais são compartilhadas.

Obrigado.

Esta é uma diferença bastante famosa entre sistemas Windows e Unix-like.

Não importa o que:

  • Cada processo tem seu próprio espaço de endereço, o que significa que nunca há memory compartilhada entre processos (a menos que você use alguma biblioteca ou extensão de comunicação entre processos).
  • A regra de definição única (ODR) ainda se aplica, o que significa que você só pode ter uma definição da variável global visível no link-time (vinculação estática ou dinâmica).

Então, a questão chave aqui é realmente a visibilidade .

Em todos os casos, variables ​​globais static (ou funções) nunca são visíveis de fora de um módulo (dll / so ou executável). O padrão C ++ requer que eles tenham binding interna, o que significa que eles não são visíveis fora da unidade de tradução (que se torna um arquivo de object) no qual eles são definidos. Então, isso resolve esse problema.

Onde fica complicado é quando você tem variables ​​globais extern . Aqui, os sistemas Windows e Unix são completamente diferentes.

No caso do Windows (.exe e .dll), as variables ​​globais extern não fazem parte dos símbolos exportados. Em outras palavras, diferentes módulos não estão de modo algum conscientes das variables ​​globais definidas em outros módulos. Isso significa que você obterá erros de vinculação se tentar, por exemplo, criar um executável que deve usar uma variável extern definida em uma DLL, porque isso não é permitido. Você precisaria fornecer um arquivo de object (ou biblioteca estática) com uma definição dessa variável externa e vinculá-lo estaticamente com o executável e a DLL, resultando em duas variables ​​globais distintas (uma pertencente ao executável e uma pertencente à DLL ).

Para realmente exportar uma variável global no Windows, você precisa usar uma syntax semelhante à da function export / import syntax, por exemplo:

 #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 você faz isso, a variável global é adicionada à lista de símbolos exportados e pode ser vinculada como todas as outras funções.

No caso de ambientes semelhantes ao Unix (como o Linux), as bibliotecas dinâmicas, chamadas “objects compartilhados” com extensão .so exportam todas as variables ​​globais extern (ou funções). Nesse caso, se você fizer o link de tempo de carregamento de qualquer lugar para um arquivo de object compartilhado, as variables ​​globais serão compartilhadas, ou seja, vinculadas juntas como uma. Basicamente, os sistemas Unix-like são projetados para fazer com que virtualmente não haja diferença entre o link com uma biblioteca estática ou dinâmica. Novamente, o ODR se aplica em toda a linha: uma variável global extern será compartilhada entre os módulos, o que significa que ela deve ter apenas uma definição em todos os módulos carregados.

Finalmente, em ambos os casos, para sistemas Windows ou Unix-like, é possível fazer link de tempo de execução da biblioteca dinâmica, ou seja, usando LoadLibrary() / GetProcAddress() / FreeLibrary() ou dlopen() / dlsym() / dlclose() . Nesse caso, você precisa obter manualmente um ponteiro para cada um dos símbolos que deseja usar e isso inclui as variables ​​globais que você deseja usar. Para variables ​​globais, você pode usar GetProcAddress() ou dlsym() da mesma forma que faz para funções, desde que as variables ​​globais façam parte da lista de símbolos exportados (pelas regras dos parágrafos anteriores).

E, claro, como nota final necessária: as variables ​​globais devem ser evitadas . E acredito que o texto que você citou (sobre as coisas serem “incertas”) está se referindo exatamente às diferenças específicas da plataforma que acabei de explicar (bibliotecas dinâmicas não são realmente definidas pelo padrão C ++, isso é território específico da plataforma, significando isso é muito menos confiável / portátil).