【问题标题】:What happens to global and static variables in a shared library when it is dynamically linked?动态链接共享库中的全局变量和静态变量会发生什么情况?
【发布时间】:2023-03-20 11:47:01
【问题描述】:

我试图了解当具有全局变量和静态变量的模块动态链接到应用程序时会发生什么。 我所说的模块是指解决方案中的每个项目(我经常使用 Visual Studio!)。这些模块要么内置在 *.lib 或 *.dll 中,要么内置在 *.exe 本身中。

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

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

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

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

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

(请同时说明理由)

引用MSDN:

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

来自here

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

谢谢。

【问题讨论】:

  • 通过 modules 你可能是指 libs。有一个提议将 modules 添加到 C++ 标准中,对模块的定义和语义比现在的常规库有更精确的定义。
  • 啊,应该澄清一下。我将解决方案中的不同项目(我经常使用 Visual Studio)视为模块。这些模块内置在 *.lib 或 *.dll 中。
  • @DavidRodríguez-dribeas 术语“模块”是独立(完全链接)可执行文件的正确技术术语,包括:可执行程序、动态链接库 (.dll) 或共享对象 (.so )。在这里用得恰到好处,意思是正确的,很好理解。正如我所解释的,在有一个名为“模块”的标准功能之前,它的定义仍然是传统的。

标签: c++ linker global-variables global dynamic-linking


【解决方案1】:

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

无论如何:

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

所以,这里的关键问题真的是可见性

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

当您拥有extern 全局变量时,情况会变得复杂。在这里,Windows 和类 Unix 系统完全不同。

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

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

#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() / @987654331 @ 或 dlopen() / dlsym() / dlclose()。在这种情况下,您必须手动获取指向您希望使用的每个符号的指针,其中包括您希望使用的全局变量。对于全局变量,您可以像使用函数一样使用GetProcAddress()dlsym(),前提是全局变量是导出符号列表的一部分(根据前几段的规则)。

当然,最后需要注意的是:应避免使用全局变量。而且我相信您引用的文本(关于“不清楚”的事情)完全是指我刚刚解释的特定于平台的差异(动态库并未真正由 C++ 标准定义,这是特定于平台的领域,这意味着它可靠性/便携性要差得多)。

【讨论】:

  • 很好的答案,谢谢!我有一个跟进:由于 DLL 是一段自包含的代码和数据,它是否有类似于可执行文件的数据段部分?我试图了解使用共享库时这些数据的加载位置和方式。
  • @Raja 是的,DLL 有一个数据段。事实上,就文件本身而言,可执行文件和 DLL 几乎相同,唯一真正的区别是在可执行文件中设置了一个标志,表示它包含“主”函数。当一个进程加载一个 DLL 时,它的数据段被复制到进程的地址空间的某个地方,并且静态初始化代码(它将初始化非平凡的全局变量)也在进程的地址空间内运行。加载与可执行文件相同,只是扩展了进程地址空间而不是创建新地址空间。
  • 类的内联函数中定义的静态变量怎么样?例如在头文件中定义“class A{ void foo() { static int st_var = 0; } }”并将其包含在模块A和模块B中,A/B会共享相同的st_var还是每个都有自己的副本?
  • @camino 如果类被导出(即使用__attribute__((visibility("default"))) 定义),那么 A/B 将共享相同的 st_var。但如果类是用__attribute__((visibility("hidden"))) 定义的,那么模块A 和模块B 将有自己的副本,而不是共享的。
  • @camino __declspec(dllexport)
【解决方案2】:

Mikael Persson 留下的答案虽然非常彻底,但在全局变量方面存在严重错误(或至少具有误导性),需要清除。最初的问题是询问是否存在单独的全局变量副本,或者是否在进程之间共享全局变量。

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

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

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

因此,与全局变量的外部导出有关的整个答案确实是题外话,没有必要,甚至与原始问题无关。由于全局变量不需要访问外部变量,因此始终可以通过对库的函数调用间接访问全局变量。

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

相反,静态库具有已烘焙到可执行文件(ELF、PE 等)中的每个进程的代码副本,当然,与动态库一样,每个进程都有单独的全局变量。

【讨论】:

  • 谢谢!我很困惑 ODR 和名称可见性与任何事情有什么关系。
【解决方案3】:

在 unix 系统中:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-17
    • 2020-11-05
    • 1970-01-01
    相关资源
    最近更新 更多