这是 Windows 和类 Unix 系统之间非常著名的区别。
无论:
- 每个进程都有自己的地址空间,这意味着进程之间永远不会共享任何内存(除非您使用一些进程间通信库或扩展)。
- 单一定义规则(ODR) 仍然适用,这意味着您只能在链接时看到全局变量的一个定义(静态或动态链接)。
所以,这里的关键问题是真正的可见性。
在所有情况下,static
全局变量(或函数)永远不会从模块外部(dll/so 或可执行文件)可见。C++ 标准要求它们具有内部链接,这意味着它们在定义它们的翻译单元(成为目标文件)之外不可见。所以,这就解决了这个问题。
当你有extern
全局变量时,它变得复杂。在这里,Windows 和类 Unix 系统完全不同。
对于 Windows(.exe 和 .dll),extern
全局变量不是导出符号的一部分。换句话说,不同的模块根本不知道其他模块中定义的全局变量。这意味着,例如,如果您尝试创建一个应该使用extern
DLL 中定义的变量的可执行文件,您将收到链接器错误,因为这是不允许的。您需要提供具有该外部变量定义的目标文件(或静态库)并将其与可执行文件和 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 系统,您都可以执行动态库的运行时链接,即使用LoadLibrary()
/ GetProcAddress()
/FreeLibrary()
或dlopen()
/ dlsym()
/ dlclose()
。在这种情况下,您必须手动获取指向您希望使用的每个符号的指针,其中包括您希望使用的全局变量。对于全局变量,只要全局变量是导出符号列表的一部分(根据前几段的规则) ,您就可以使用GetProcAddress()
或与函数一样使用。dlsym()
当然,作为必要的最后说明:应避免使用全局变量。而且我相信您引用的文本(关于“不清楚”的事情)完全是指我刚刚解释的特定于平台的差异(动态库并未真正由 C++ 标准定义,这是特定于平台的领域,这意味着它可靠性/便携性要低得多)。