我试图了解当具有全局变量和静态变量的模块动态链接到应用程序时会发生什么。我所说的模块是指解决方案中的每个项目(我经常使用 Visual Studio!)。这些模块或者内置在 *.lib 或 *.dll 或 *.exe 本身中。
我了解应用程序的二进制文件包含数据段中所有单个翻译单元(目标文件)的全局和静态数据(如果为 const,则为只读数据段)。
当这个应用程序使用带有加载时动态链接的模块 A 时会发生什么?我假设 DLL 有一个用于其全局和静态的部分。操作系统会加载它们吗?如果是这样,它们会加载到哪里?
当应用程序使用带有运行时动态链接的模块 B 时会发生什么?
如果我的应用程序中有两个同时使用 A 和 B 的模块,是否按如下所述创建了 A 和 B 的全局变量的副本(如果它们是不同的进程)?
DLL A 和 B 是否可以访问应用程序全局变量?
(请同时说明你的理由)
引用 MSDN :
在 DLL 源代码文件中声明为全局的变量会被编译器和链接器视为全局变量,但每个加载给定 DLL 的进程都会获得它自己的该 DLL 全局变量的副本。静态变量的范围仅限于声明静态变量的块。因此,默认情况下,每个进程都有自己的 DLL 全局和静态变量实例。
从 这里开始:
当动态链接模块时,可能不清楚不同的库是否有自己的全局实例或全局是否共享。
谢谢。
原文由 Raja 发布,翻译遵循 CC BY-SA 4.0 许可协议
这是 Windows 和类 Unix 系统之间非常著名的区别。
无论:
所以,这里的关键问题是真正的 _可见性_。
在所有情况下,
static
全局变量(或函数)永远不会从模块外部(dll/so 或可执行文件)看到。 C++ 标准要求它们具有内部链接,这意味着它们在定义它们的翻译单元(成为目标文件)之外不可见。所以,这就解决了这个问题。当您拥有
extern
全局变量时,情况会变得复杂。在这里,Windows 和类 Unix 系统完全不同。对于 Windows(.exe 和 .dll),
extern
全局变量不是导出符号的一部分。换句话说,不同的模块根本不知道其他模块中定义的全局变量。这意味着如果您尝试创建一个可执行文件,该可执行文件应该使用在 DLL 中定义的extern
变量,您将收到链接器错误,因为这是不允许的。您需要提供一个包含该外部变量定义的目标文件(或静态库),并将 其 与可执行文件和 DLL 静态链接,从而产生两个不同的全局变量(一个属于可执行文件,一个属于 DLL )。要在 Windows 中实际导出全局变量,您必须使用类似于函数导出/导入语法的语法,即:
当您这样做时,全局变量将添加到导出符号列表中,并且可以像所有其他函数一样链接。
在类 Unix 环境(如 Linux)的情况下,动态库称为“共享对象”,扩展名为
.so
导出所有extern
全局变量(或函数)。在这种情况下,如果您从任何地方执行 加载时 链接到共享对象文件,则全局变量是共享的,即作为一个链接在一起。基本上,类 Unix 系统的设计目的是使与静态库或动态库的链接几乎没有区别。同样,ODR 全面适用:一个extern
全局变量将在模块之间共享,这意味着它应该在所有加载的模块中只有一个定义。最后,在这两种情况下,对于 Windows 或类 Unix 系统,您可以执行动态库的 运行时 链接,即使用
LoadLibrary()
/GetProcAddress()
/FreeLibrary()
或dlopen()
/dlsym()
/dlclose()
。在这种情况下,您必须手动获取指向您希望使用的每个符号的指针,其中包括您希望使用的全局变量。对于全局变量,您可以使用GetProcAddress()
或dlsym()
就像对函数一样,前提是全局变量是导出符号列表的一部分(根据前面的规则段)。当然,作为必要的最后说明: 应避免使用全局变量。而且我相信您引用的文本(关于“不清楚”的事情)正是指我刚刚解释的特定于平台的差异(动态库并未真正由 C++ 标准定义,这是特定于平台的领域,这意味着它可靠性/便携性要低得多)。