O que acontece com variáveis ​​estáticas e globais em uma biblioteca compartilhada quando ela é vinculada dinamicamente?

127

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

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

  • O que acontece quando este aplicativo usa um módulo A com vinculação dinâmica em tempo de carregamento? Presumo que a DLL tenha uma seção para seus globais e estáticos. O sistema operacional os carrega? Se sim, para 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 no meu aplicativo que usam A e B, as cópias dos globais de A e B são criadas conforme mencionado abaixo (se forem processos diferentes)?

  • As DLLs A e B têm acesso aos aplicativos globais?

(Por favor, indique seus motivos também)

Citando no MSDN :

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

e daqui :

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

Obrigado.

Rajá
fonte
3
Por módulos, você provavelmente quer dizer libs . Existe uma proposta para adicionar módulos ao padrão C ++ com uma definição mais precisa do que seria um módulo e semântica diferente das bibliotecas regulares a partir de agora.
David Rodríguez - dribeas 15/10
Ah, deveria ter esclarecido isso. Considero diferentes projetos em uma solução (trabalho muito com o visual studio) como módulos. Esses módulos são incorporados nos arquivos * .lib ou * .dll.
Raja
3
@ DavidRodríguez-dribeas O termo "módulo" é o termo técnico correto para arquivos executáveis ​​independentes (totalmente vinculados), incluindo: programas executáveis, bibliotecas de vínculo dinâmico (.dll) ou objetos compartilhados (.so). É perfeitamente apropriado aqui, e o significado é correto e bem compreendido. Até que haja um recurso padrão chamado "modules", a definição dele permanece a tradicional, como expliquei.
Mikael Persson

Respostas:

176

Essa é uma diferença bastante famosa entre os sistemas Windows e Unix.

Não importa o que:

  • Cada processo possui seu próprio espaço de endereço, o que significa que nunca há nenhuma memória sendo compartilhada entre os processos (a menos que você use alguma biblioteca ou extensões de comunicação entre processos).
  • A Regra de Uma Definição (ODR) ainda se aplica, o que significa que você só pode ter uma definição da variável global visível no momento do link (vinculação estática ou dinâmica).

Portanto, a questão principal aqui é realmente a visibilidade .

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

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

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

Para realmente exportar uma variável global no Windows, você deve usar uma sintaxe semelhante à função exportar / importar sintaxe, ou seja:

#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 do tipo Unix (como Linux), as bibliotecas dinâmicas, chamadas "objetos compartilhados" com extensão, .soexportam todas externas variáveis ​​(ou funções) globais. Nesse caso, se você vincular o tempo de carregamento de qualquer lugar a um arquivo de objeto compartilhado, as variáveis ​​globais serão compartilhadas, ou seja, vinculadas como um. Basicamente, sistemas do tipo Unix são projetados para torná-lo de forma que não haja virtualmente nenhuma diferença entre vincular-se a uma biblioteca estática ou dinâmica. Novamente, o ODR se aplica de maneira geral: uma externvariável global será compartilhada entre os módulos, o que significa que ela deve ter apenas uma definição em todos os módulos carregados.

Finalmente, nos dois casos, para sistemas Windows ou Unix, você pode vincular a biblioteca dinâmica em tempo de execução , 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 variáveis ​​globais que você deseja usar. Para variáveis ​​globais, você pode usar GetProcAddress()ou dlsym()da mesma forma que para funções, desde que as variáveis ​​globais façam parte da lista de símbolos exportados (pelas regras dos parágrafos anteriores).

E, claro, como uma nota final necessária: variáveis ​​globais devem ser evitadas . E acredito que o texto que você citou (sobre coisas que não são claras) se refere exatamente às diferenças específicas da plataforma que acabei de explicar (as bibliotecas dinâmicas não são realmente definidas pelo padrão C ++, este é um território específico da plataforma, ou seja, é muito menos confiável / portátil).

Mikael Persson
fonte
5
Ótima resposta, obrigado! Eu tenho um acompanhamento: Como a DLL é uma parte independente de código e dados, ela possui uma seção de segmento de dados semelhante aos executáveis? Estou tentando entender onde e como esses dados são carregados (para) quando a biblioteca compartilhada é usada.
Raja
18
@ Raja Sim, a DLL possui um segmento de dados. De fato, em termos de arquivos, executáveis ​​e DLLs são praticamente idênticos, a única diferença real é um sinalizador definido no executável para dizer que ele contém uma função "principal". Quando um processo carrega uma DLL, seu segmento de dados é copiado em algum lugar no espaço de endereço do processo e o código de inicialização estático (que inicializaria variáveis ​​globais não triviais) também é executado no espaço de endereço do processo. O carregamento é o mesmo do executável, exceto que o espaço de endereço do processo é expandido em vez de criado um novo.
Mikael Persson
4
E as variáveis ​​estáticas definidas dentro de uma função embutida de uma classe? por exemplo, defina "classe A {void foo () {static int st_var = 0;}}" no arquivo de cabeçalho e inclua-o no módulo A e no módulo B, o A / B compartilhará o mesmo st_var ou cada um terá sua própria cópia?
camino
2
@camino Se a classe for exportada (ou seja, definida com __attribute__((visibility("default")))), A / B compartilhará o mesmo st_var. Mas se a classe for definida com __attribute__((visibility("hidden"))), o módulo A e o módulo B terão sua própria cópia, não compartilhada.
Wei Guo
1
@camino __declspec (dllexport)
ruipacheco