当前位置: 首页 > 知识库问答 >
问题:

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

常鸿朗
2023-03-14

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

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

>

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

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

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

    DLLs A和B可以访问应用程序全局变量吗?

    (请说明理由)

    引用自MSDN:

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

    从这里开始:

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

    谢谢。

  • 共有3个答案

    公孙芷阳
    2023-03-14

    在unix系统中:

    需要注意的是,如果两个动态库导出相同的全局变量,链接器不会抱怨。但在执行期间,可能会出现 segfault,具体取决于访问冲突。表现出此行为的常见数字是分段错误 15

    segfault at xxxxxx ip xxxxxx sp xxxxxxx error 15 in a.out
    
    田志
    2023-03-14

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

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

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

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

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

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

    相反,一个静态库有一份每个进程的代码拷贝,已经被放入可执行文件(ELF,PE,等等)。),当然,像动态库一样,每个进程都有单独的全局变量。

    杨乐意
    2023-03-14

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

    无论如何:

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

    因此,这里的关键问题是可见性。

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

    复杂的地方是当您有<code>extern<code>全局变量时。在这里,Windows和类Unix系统完全不同。

    在Windows(.exe和。dll),< code>extern全局变量不是导出符号的一部分。换句话说,不同的模块根本不知道其他模块中定义的全局变量。这意味着,如果您尝试创建一个应该使用DLL中定义的< code>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适用于所有领域:extern全局变量将跨模块共享,这意味着它应该在所有加载的模块中只有一个定义。

    最后,在这两种情况下,对于类似Windows或Unix的系统,您可以进行动态库的运行时链接,即使用< code > LoadLibrary()/< code > GetProcAddress()/< code > free library()或< code > dlopen()/< code > dlsym()/< code > dl close()。在这种情况下,您必须手动获取一个指向您希望使用的每个符号的指针,其中包括您希望使用的全局变量。对于全局变量,您可以使用< code>GetProcAddress()或< code>dlsym(),就像您对函数所做的一样,前提是全局变量是导出符号列表的一部分(根据前面段落的规则)。

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

     类似资料:
    • 问题内容: 我只想在进程与该进程调用的dll之间共享静态/全局变量。exe和dll在相同的内存地址空间中。我不希望在其他进程之间共享该变量。 问题的阐述: 说是有一个静态/全局变量在。exe 和dll 都具有,因此变量都在两个图像中。 现在,动态加载(或静态)。然后,问题是该变量是否由exe和dll共享。 在Windows中,这两个家伙 永远不会 共享:exe和dll将具有的单独副本。但是,在Li

    • 问题内容: 在下面的Java示例中,谁能确切解释为什么程序输出为“ Orange”?(这是一个面试问题) 问题答案: 变量Y掩盖了类型Y。请参见JLS: 6.4.2。遮盖 简单名称可能出现在可能被解释为变量,类型或包的名称的上下文中。在这些情况下,第6.5节的规则指定将优先于类型选择变量,并优先于包选择类型。因此,有时可能无法通过其简单名称引用可见类型或程序包声明。我们说这样的声明是模糊的。 合格

    • 所以,我试图弄清楚Beam DoFn中静态变量的行为,它在线程之间共享(在同一个JVM中)吗? 基本上是试图从编程指南中了解以下内容: 4.3.2.线程兼容性 …请注意,函数对象中的静态成员不会传递给工作实例,并且可以从不同的线程访问函数的多个实例。 https://beam.apache.org/documentation/programming-guide/#requirements-用于编写

    • 问题内容: 我已经定义了一个对象并声明了一个静态变量。在该方法中,当我尝试打印实例和类变量时,两者都打印相同的值。 不是实例变量吗?它应该打印0而不是50吗? 问题答案: 不,只有一个变量-您尚未声明任何实例变量。 不幸的是,Java允许您访问静态成员,就像通过相关类型的引用访问静态成员一样。这是IMO的设计缺陷,某些IDE(例如Eclipse)允许您将其标记为警告或错误- 但这是语言的一部分。您

    • 问题内容: 我是Java编程的新手。谁能说出Java中的全局变量和局部变量之间的区别? 问题答案: 您的问题有点困惑,因为您在标题中引用的是static / global,而在问题中引用的是global / local。 变量绑定到一个 类 , 每个类 将有 一个实例 。 类可以具有成员变量,并且该类的 每个实例 将有 一个实例 。 请注意,如果您有多个类加载器,这将变得更加复杂。在这种情况下,您