当共享库被动态链接时,全局变量和静态变量会发生什么情况?

我试图理解当具有全局变量和静态变量的模块动态链接到应用程序时会发生什么。 所谓模块,我指的是解决方案中的每个项目(我经常使用 Visual Studio!).这些模块要么内置在 * 中。Lib 还是 * 。Dll or the * .自我解放。

据我所知,应用程序的二进制文件包含数据段中所有单个翻译单元(对象文件)的全局和静态数据(如果是 const,则只读数据段)。

  • 当这个应用程序使用具有加载时动态链接的模块 A 时会发生什么?我假设 DLL 有一个全局和静态部分。操作系统是否加载它们?如果是这样,他们会被装到哪里?

  • 当应用程序使用具有运行时动态链接的模块 B 时会发生什么?

  • 如果我的应用程序中有两个模块都使用 A 和 B,那么 A 和 B 的全局变量的副本是否如下所述创建(如果它们是不同的进程) ?

  • DLL A 和 B 是否可以访问应用程序全局?

(请说明理由)

引自 MSDN:

在 DLL 源代码文件中声明为全局变量的变量被编译器和链接器视为全局变量,但是加载给定 DLL 的每个进程都会获得该 DLL 全局变量的自己的副本。静态变量的作用域仅限于声明静态变量的块。因此,默认情况下,每个进程都有自己的 DLL 全局和静态变量实例。

以及 给你台:

在动态链接模块时,可能不清楚不同的库是否有自己的全局变量实例,或者全局变量是否共享。

谢谢。

118087 次浏览

这是 Windows 和类 Unix 系统之间一个非常著名的区别。

无论如何:

  • 每个 程序都有自己的地址空间,这意味着进程之间永远不会共享任何内存(除非使用一些行程间通讯库或扩展)。
  • 一个定义规则(ODR)仍然适用,这意味着在链接时只能有一个全局变量的可见定义(静态或动态链接)。

所以,这里的关键问题是 能见度

在所有情况下,static全局变量(或函数)从模块外部(dll/so 或可执行文件)是不可见的。C + + 标准要求它们具有内部链接,这意味着它们在定义它们的翻译单元(变成对象文件)之外是不可见的。那么,这个问题就解决了。

当你使用 extern全局变量的时候,情况就变得复杂了。在这里,Windows 和 Unix 系统是完全不同的。

对于 Windows (。前任和。Dll) ,extern全局变量不是导出符号的一部分。换句话说,不同的模块不知道其他模块中定义的全局变量。这意味着,如果尝试创建一个应该使用 DLL 中定义的 extern变量的可执行文件,就会出现链接器错误,因为这是不允许的。您需要提供一个对象文件(或静态库) ,其中包含外部变量的定义,并将其与可执行文件 都有和 DLL 静态链接,从而产生两个不同的全局变量(一个属于可执行文件,另一个属于 DLL)。

要在 Windows 中实际导出全局变量,必须使用类似于函数 export/import 语法的语法,即:

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

当您这样做时,全局变量将被添加到导出符号列表中,并且可以像所有其他函数一样进行链接。

在类 Unix 环境(如 Linux)中,称为“共享对象”的动态库(扩展名为 .so)导出所有 extern全局变量(或函数)。在这种情况下,如果从任何地方将 加载时间链接到共享对象文件,那么全局变量将被共享,即作为一个整体链接在一起。基本上,类 Unix 系统的设计目的就是让它在链接静态库和动态库之间几乎没有区别。同样,ODR 适用于所有模块: extern全局变量将在模块之间共享,这意味着它应该在所有加载的模块之间只有一个定义。

最后,在这两种情况下,对于 Windows 或类 Unix 系统,您都可以对动态库进行 运行时间链接,即使用 LoadLibrary()/GetProcAddress()/FreeLibrary()dlopen()/dlsym()/dlclose()。在这种情况下,必须手动获得指向希望使用的每个符号的指针,其中包括希望使用的全局变量。对于全局变量,您可以像对函数一样使用 GetProcAddress()dlsym(),前提是全局变量是导出符号列表的一部分(按照前面的规则)。

当然,作为一个必要的最后注意: 应避免使用全局变量。我相信你引用的文本(关于“不清楚”的内容)正是指我刚才解释的平台特有的差异(动态库并不是真正由 C + + 标准定义的,这是平台特有的领域,意味着它不太可靠/可移植)。

在 unix 系统中:

需要注意的是,如果两个动态库导出相同的全局变量,链接器不会抱怨。但是在执行过程中,根据访问冲突,可能会出现 Segfault。表现出这种行为的通常数字是内存区段错误15

segfault at xxxxxx ip xxxxxx sp xxxxxxx error 15 in a.out

迈克尔 · 佩尔松留下的答案虽然非常全面,但是在全局变量方面有一个严重的错误(或者至少是误导性的) ,需要加以澄清。最初的问题是,全局变量是否有单独的副本,或者进程之间是否共享全局变量。

真正的答案如下: 每个进程都有独立(多个)的全局变量副本,并且它们不在进程之间共享。因此,声明一个定义规则(ODR)适用也是非常误导,它不适用的意义,他们不是每个进程使用的相同的全局,所以在现实中它不是“一个定义”进程之间

此外,即使全局变量对进程来说不是“可见的”,。.它们总是很容易被进程“访问”,因为任何函数都可以很容易地向进程返回全局变量的值,或者说,进程可以通过函数调用设置全局变量的值。因此,这个答案也具有误导性。

实际上,“ yes”进程确实拥有对全局变量的完全“访问”,至少通过对库的函数调用是这样的。但是重申一下,每个进程都有自己的全局变量副本,所以它不会与另一个进程使用的全局变量相同。

因此,有关全球对外出口的整个答案确实离题了,而且是不必要的,甚至与最初的问题没有关系。因为全局变量不需要外部变量来访问,所以总是可以通过对库的函数调用间接访问全局变量。

当然,进程之间共享的唯一部分是实际的“代码”。代码只装载在物理内存(RAM)中的一个位置,但是相同的物理内存位置当然会映射到每个进程的“本地”虚拟内存位置。

相反,静态库拥有已经内置到可执行文件(ELF、 PE 等)中的每个进程的代码副本,当然,就像动态库拥有针对每个进程的独立全局变量一样。