c++ 当一个共享库被动态链接时,它的全局变量和静态变量会发生什么变化?

qzwqbdag  于 2023-04-13  发布在  其他
关注(0)|答案(3)|浏览(487)

我试图理解当带有全局变量和静态变量的模块动态链接到应用程序时会发生什么。我所说的模块,指的是解决方案中的每个项目(我经常使用visual studio!)。这些模块要么内置在 *.lib或 *.dll中,要么内置在 *.exe本身中。
我知道应用程序的二进制文件在数据段(如果是const,则为只读数据段)中包含所有单个翻译单元(目标文件)的全局和静态数据。

  • 当这个应用程序使用一个带有加载时动态链接的模块A时会发生什么?我假设DLL有一个用于全局变量和静态变量的部分。操作系统会加载它们吗?如果是,它们会被加载到哪里?
  • 当应用程序使用带有运行时动态链接的模块B时会发生什么?
  • 如果我的应用程序中有两个模块都使用了A和B,那么A和B的全局变量的副本是否按照下面提到的方式创建(如果它们是不同的进程)?
  • DLL A和B是否可以访问应用程序全局变量?

(请同时说明理由)
引用自MSDN
在DLL源代码文件中声明为全局变量的变量被编译器和链接器视为全局变量,但加载给定DLL的每个进程都获得该DLL的全局变量的自己的副本。静态变量的范围限于声明静态变量的块。因此,默认情况下,每个进程都有自己的DLL全局变量和静态变量的示例。
here
当动态链接模块时,可能不清楚不同的库是否有自己的全局变量示例,或者全局变量是否共享。
谢谢。

avwztpqn

avwztpqn1#

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

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

所以,这里的关键问题是真正的“可见性”。
在所有情况下,static全局变量(或函数)从模块(dll/so或可执行文件)外部永远不可见。C++标准要求这些变量具有内部链接,这意味着它们在定义它们的转换单元(成为目标文件)之外不可见。因此,这解决了这个问题。
当你有extern全局变量的时候,事情就变得复杂了。
对于Windows(.exe和.dll),extern全局变量不是导出符号的一部分。换句话说,不同的模块不知道其他模块中定义的全局变量。这意味着如果您尝试,例如,创建一个应该使用DLL中定义的extern变量的可执行文件,则会出现链接器错误。因为这是不允许的。你需要提供一个对象文件(或静态库),其中包含该外部变量的定义,并将其与可执行文件和DLL静态链接,从而产生两个不同的全局变量(一个属于可执行文件,另一个属于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全局变量(或函数)。在这种情况下,如果您从任何地方进行 load-time 链接到共享对象文件,则全局变量是共享的,即链接在一起。基本上,类Unix系统的设计目的是使其与静态库或动态库的链接几乎没有区别。ODR同样适用于所有方面:extern全局变量将在模块间共享,这意味着它应该在所有加载的模块间只有一个定义。
最后,在这两种情况下,对于Windows或类Unix系统,您可以进行动态库的 * 运行时 * 链接,即使用LoadLibrary()/GetProcAddress()/FreeLibrary()dlopen()/dlsym()/dlclose()。在这种情况下,您必须手动获取指向您希望使用的每个符号的指针,对于全局变量,你可以使用GetProcAddress()dlsym(),就像你对函数所做的那样,只要全局变量是导出符号列表的一部分(根据前面段落的规则)。
当然,作为必要的最后说明:应该避免全局变量.我相信你引用的文字(关于事情“不清楚”)正是指我刚才解释的特定于平台的差异(动态库并不是真正由C++标准定义的,这是特定于平台的领域,这意味着它的可靠性/可移植性要低得多)。

ltskdhd1

ltskdhd12#

Mikael Persson留下的答案虽然非常彻底,但在全局变量方面包含了一个严重的错误(或者至少是误导性的),需要澄清。最初的问题是问全局变量是否有单独的副本,或者全局变量是否在进程之间共享。
真正的答案如下:每个进程都有单独的(多个)全局变量副本,并且它们不在进程之间共享。因此,通过声明一个定义规则(ODR)适用也是非常误导的,它并不适用于每个进程使用的全局变量,因此实际上它不是进程之间的“一个定义”**。
此外,即使全局变量对进程不可见,..它们总是很容易被进程“访问”,因为任何函数都可以很容易地将全局变量的值返回给进程,或者就此而言,进程可以通过函数调用来设置全局变量的值。因此,这个答案也是误导性的。
实际上,进程确实可以完全“访问”全局变量,至少可以通过函数调用库来访问全局变量。但是重申一下,每个进程都有自己的全局变量副本,所以它不会是另一个进程正在使用的相同全局变量。
因此,关于全局变量的外部导出的整个答案真的偏离了主题,没有必要,甚至与原始问题无关。因为全局变量不需要外部变量来访问,所以全局变量总是可以通过函数调用库间接访问。
当然,进程之间共享的唯一部分是实际的“代码”。代码只加载在物理内存(RAM)中的一个位置,但同一物理内存位置当然被Map到每个进程的“本地”虚拟内存位置。
相反,静态库拥有每个进程(ELF、PE等)的代码副本,这些代码已经被烘焙到可执行文件中,当然,与动态库一样,每个进程都有单独的全局变量。

fwzugrvs

fwzugrvs3#

unix系统下:

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

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

相关问题