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.
Respostas:
Essa é uma diferença bastante famosa entre os sistemas Windows e Unix.
Não importa o que:
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
extern
variá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 umaextern
variá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:
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,
.so
exportam todasextern
as 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: umaextern
variá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()
oudlopen()
/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 usarGetProcAddress()
oudlsym()
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).
fonte
__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.