2013-10-15 55 views
74

我想了解当全局变量和静态变量模块动态链接到应用程序时会发生什么。 通过模块,我指的是解决方案中的每个项目(我在Visual Studio中工作过很多!)。这些模块内置于* .lib或* .dll或* .exe本身。动态链接时共享库中的全局变量和静态变量会发生什么变化?

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

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

  • 当应用程序使用与运行时动态链接一个模块B会发生什么?

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

  • 待办事项的DLL A和B可以访问应用程序的全局变量?

(请说出你的理由,以及)

MSDN报价:

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

和从here

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

谢谢。

+0

*模块*你可能是指* libs *。有一个建议,在C++标准中增加* modules *,并且对于模块的定义和截至目前的常规库有着更为精确的定义。 –

+0

啊,应该澄清一点。我将解决方案中的不同项目(我在Visual Studio中工作很多)视为模块。这些模块内置到* .lib或* .dll中。 – Raja

+0

@DavidRodríguez-dribeas术语“模块”是独立(完全链接的)可执行文件的正确术语,包括:可执行程序,动态链接库(.dll)或共享对象(.so)。这里非常合适,意思是正确的,并且很好理解。正如我所解释的,在有一个名为“模块”的标准功能之前,其定义仍然是传统的功能。 –

回答

120

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

不管是什么:

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

所以,问题的关键是在这里真的知名度

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

它变得复杂的是当你有全局变量extern。在这里,Windows和类Unix系统完全不同。

对于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全局变量(或函数)。在这种情况下,如果你从任何地方连接到共享目标文件,那么全局变量是共享的,即作为一个链接在一起。基本上,类Unix系统的设计使其与静态或动态库链接几乎没有区别。同样,ODR全面应用:全局变量将在模块之间共享,这意味着它应该只在加载的所有模块中具有一个定义。

最后,在两种情况下,Windows或Unix类系统中,你可以做运行时联的动态库的,即无论是使用LoadLibrary()/GetProcAddress()/FreeLibrary()dlopen()/dlsym()/dlclose()。在这种情况下,您必须手动获取您希望使用的每个符号的指针,其中包含您希望使用的全局变量。对于全局变量,只要全局变量是导出符号列表的一部分(按照前面段落的规则),就可以使用GetProcAddress()dlsym(),就像您对函数做的一样。

当然,作为必要的最后说明:应该避免全局变量。我相信你所引用的文本(关于事物“不清楚”)完全是指我刚刚解释的特定于平台的差异(动态库并非真正由C++标准定义,这是特定于平台的领域,意味着它更不可靠/便携)。

+2

很好的答案,谢谢!我有一个后续:因为DLL是一个自包含的代码和数据,它是否有一个类似于可执行文件的数据段部分?我试图了解何时以及如何在使用共享库时加载(到)数据。 – Raja

+8

@Raja是的,该DLL有一个数据段。实际上,就文件本身而言,可执行文件和DLL实际上是相同的,唯一真正的区别是在可执行文件中设置了一个标志,表示它包含“main”函数。当进程加载DLL时,其数据段被复制到进程的地址空间中,并且静态初始化代码(它将初始化非平凡的全局变量)也在进程的地址空间中运行。加载与可执行文件的加载相同,只是进程地址空间被扩展而不是创建一个新的。 –

+0

谢谢澄清。 – Raja