动态链接共享库中的全局变量和静态变量会发生什么情况?
我试图了解当具有全局变量和静态变量的模块动态链接到应用程序时会发生什么.我所说的模块是指解决方案中的每个项目(我经常使用 Visual Studio!).这些模块要么内置在 *.lib 或 *.dll 中,要么内置在 *.exe 本身中.
I'm trying to understand what happens when modules with globals and static variables are dynamically linked to an application. By modules, I mean each project in a solution (I work a lot with visual studio!). These modules are either built into *.lib or *.dll or the *.exe itself.
我了解应用程序的二进制文件包含数据段中所有单个翻译单元(目标文件)的全局和静态数据(如果为 const,则为只读数据段).
I understand that the binary of an application contains global and static data of all the individual translation units (object files) in the data segment (and read only data segment if const).
当这个应用程序使用带有加载时动态链接的模块 A 时会发生什么?我假设 DLL 有一个用于其全局和静态的部分.操作系统会加载它们吗?如果是这样,它们会被加载到哪里?
What happens when this application uses a module A with load-time dynamic linking? I assume the DLL has a section for its globals and statics. Does the operating system load them? If so, where do they get loaded to?
当应用程序使用带有运行时动态链接的模块 B 时会发生什么?
And what happens when the application uses a module B with run-time dynamic linking?
如果我的应用程序中有两个模块同时使用 A 和 B,是否会按如下所述创建 A 和 B 的全局变量的副本(如果它们是不同的进程)?
If I have two modules in my application that both use A and B, are copies of A and B's globals created as mentioned below (if they are different processes)?
DLL A 和 B 是否可以访问应用程序全局变量?
Do DLLs A and B get access to the applications globals?
(请同时说明理由)
引用 MSDN:
在 DLL 源代码文件中声明为全局的变量会被编译器和链接器视为全局变量,但每个加载给定 DLL 的进程都会获得它自己的该 DLL 全局变量的副本.静态变量的范围仅限于声明静态变量的块.因此,默认情况下,每个进程都有自己的 DLL 全局变量和静态变量实例.
Variables that are declared as global in a DLL source code file are treated as global variables by the compiler and linker, but each process that loads a given DLL gets its own copy of that DLL's global variables. The scope of static variables is limited to the block in which the static variables are declared. As a result, each process has its own instance of the DLL global and static variables by default.
从这里:
当动态链接模块时,可能不清楚不同的库是否有自己的全局实例或全局是否共享.
When dynamically linking modules, it can be unclear whether different libraries have their own instances of globals or whether the globals are shared.
谢谢.
推荐答案
这是 Windows 和类 Unix 系统之间非常著名的区别.
This is a pretty famous difference between Windows and Unix-like systems.
无论如何:
- 每个进程都有自己的地址空间,这意味着进程之间永远不会共享任何内存(除非您使用一些进程间通信库或扩展).
- 一个定义规则 (ODR) 仍然适用,这意味着您只能有一个在链接时可见的全局变量定义(静态或动态链接).
- Each process has its own address space, meaning that there is never any memory being shared between processes (unless you use some inter-process communication library or extensions).
- The One Definition Rule (ODR) still applies, meaning that you can only have one definition of the global variable visible at link-time (static or dynamic linking).
所以,这里的关键问题真的是可见性.
So, the key issue here is really visibility.
在所有情况下,static
全局变量(或函数)永远不会从模块(dll/so 或可执行文件)外部看到.C++ 标准要求它们具有内部链接,这意味着它们在定义它们的翻译单元(成为目标文件)之外不可见.所以,这解决了这个问题.
In all cases, static
global variables (or functions) are never visible from outside a module (dll/so or executable). The C++ standard requires that these have internal linkage, meaning that they are not visible outside the translation unit (which becomes an object file) in which they are defined. So, that settles that issue.
当你有 extern
全局变量时,事情就变得复杂了.在这里,Windows 和类 Unix 系统完全不同.
Where it gets complicated is when you have extern
global variables. Here, Windows and Unix-like systems are completely different.
对于 Windows(.exe 和 .dll),extern
全局变量不是导出符号的一部分.换句话说,不同的模块根本不知道其他模块中定义的全局变量.这意味着,例如,如果您尝试创建一个应该使用 DLL 中定义的 extern
变量的可执行文件,则会出现链接器错误,因为这是不允许的.您需要提供一个包含该外部变量定义的目标文件(或静态库),并将其与可执行文件和 DLLboth 静态链接,从而产生两个不同的全局变量(一个属于可执行文件和一个属于 DLL 的文件).
In the case of Windows (.exe and .dll), the extern
global variables are not part of the exported symbols. In other words, different modules are in no way aware of global variables defined in other modules. This means that you will get linker errors if you try, for example, to create an executable that is supposed to use an extern
variable defined in a DLL, because this is not allowed. You would need to provide an object file (or static library) with a definition of that extern variable and link it statically with both the executable and the DLL, resulting in two distinct global variables (one belonging to the executable and one belonging to the DLL).
要在 Windows 中实际导出全局变量,您必须使用类似于函数导出/导入语法的语法,即:
To actually export a global variable in Windows, you have to use a syntax similar to the function export/import syntax, i.e.:
#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;
当您这样做时,全局变量将添加到导出符号列表中,并且可以像所有其他函数一样链接.
When you do that, the global variable is added to the list of exported symbols and can be linked like all the other functions.
在类 Unix 环境(如 Linux)的情况下,动态库称为共享对象",扩展名为 .so
导出所有 extern
全局变量(或职能).在这种情况下,如果您执行 load-time 从任何地方链接到共享对象文件,则全局变量是共享的,即作为一个链接在一起.基本上,类 Unix 系统的设计目的是使与静态库或动态库的链接几乎没有区别.同样,ODR 全面适用:extern
全局变量将在模块之间共享,这意味着它应该在所有加载的模块中只有一个定义.
In the case of Unix-like environments (like Linux), the dynamic libraries, called "shared objects" with extension .so
export all extern
global variables (or functions). In this case, if you do load-time linking from anywhere to a shared object file, then the global variables are shared, i.e., linked together as one. Basically, Unix-like systems are designed to make it so that there is virtually no difference between linking with a static or a dynamic library. Again, ODR applies across the board: an extern
global variable will be shared across modules, meaning that it should have only one definition across all the modules loaded.
最后,在这两种情况下,对于 Windows 或类 Unix 系统,您都可以对动态库进行 run-time 链接,即使用 LoadLibrary()
/GetProcAddress()
/FreeLibrary()
或 dlopen()
/dlsym()
/dlclose()代码>.在这种情况下,您必须手动获取指向您希望使用的每个符号的指针,其中包括您希望使用的全局变量.对于全局变量,您可以像使用函数一样使用
GetProcAddress()
或 dlsym()
,只要全局变量是导出符号列表的一部分 (根据前几段的规则).
Finally, in both cases, for Windows or Unix-like systems, you can do run-time linking of the dynamic library, i.e., using either LoadLibrary()
/ GetProcAddress()
/ FreeLibrary()
or dlopen()
/ dlsym()
/ dlclose()
. In that case, you have to manually get a pointer to each of the symbols you wish to use, and that includes the global variables you wish to use. For global variables, you can use GetProcAddress()
or dlsym()
just the same as you do for functions, provided that the global variables are part of the exported symbol list (by the rules of the previous paragraphs).
当然,最后需要注意的是:应避免使用全局变量.而且我相信您引用的文本(关于不清楚"的事情)正是指我刚刚解释的特定于平台的差异(动态库并未真正由 C++ 标准定义,这是特定于平台的领域,这意味着它可靠性/便携性要低得多).
And of course, as a necessary final note: global variables should be avoided. And I believe that the text you quoted (about things being "unclear") is referring exactly to the platform-specific differences that I just explained (dynamic libraries are not really defined by the C++ standard, this is platform-specific territory, meaning it is much less reliable / portable).
相关文章