I receive different results on UNIX and WIN when use static members with static linking of shared library to executable. Please explain why?
请考虑以下和平准则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | // 1. Single header file. Imagine that it is some static library. // Counter.h #pragma once struct Counter { Counter() { ++getCount(); } static int& getCount() { static int counter = 0; return counter; } }; // 2. Shared library (!) : // main_DLL.cpp #include <iostream> #include"counter.h" extern"C" { __declspec(dllexport) // for WIN void main_DLL() { Counter c; std::cout <<"main_DLL : ptr =" << &Counter::getCount()<<" value =" << Counter::getCount() << std::endl; } } // 3. Executable. Shared library statically (!) linked to the executable file. // main.cpp #include"counter.h" #include <iostream> extern"C" { __declspec(dllimport) // for WIN void main_DLL(); } int main() { main_DLL(); Counter c; std::cout <<"main_EXE : ptr =" << &Counter::getCount() <<" value =" << Counter::getCount() << std::endl; } |
结果:
1 2 3 4 5 6 7 8 9 | Results for WIN (Win8.1 gcc 5.1.0): main_DLL : ptr = 0x68783030 value = 1 main_EXE : ptr = 0x403080 value = 1 // conclusion: two different counters Results for UNIX (Red Hat <I don’t remember version exactly> gcc 4.8.3): main_DLL : ptr = 0x75693214 value = 1 main_EXE : ptr = 0x75693214 value = 2 // conclusion: the same counter addressed |
建筑物:
1 2 3 4 5 6 7 8 9 | Building for WIN: g++ -c -Wall -Werror -o main_DLL.o main_DLL.cpp g++ -shared -Wl,--out-implib=libsharedLib.a -o libsharedLib.so main_DLL.o g++ -Wall –Werror -o simpleExample main.cpp -L./ -lsharedLib Building for UNIX: g++ -c -Wall -Werror -fPIC -o main_DLL.o main_DLL.cpp g++ -shared -fPIC -o libsharedLib.so main_DLL.o g++ -Wall –Werror -fPIC -o simpleExample main.cpp -L./ -lsharedLib |
所以,您可以看到我在Unix上添加了-fpic,不需要为Unix创建导入库,因为所有导出符号都包含在共享库中。在Windows上,我使用declspec。
对我来说,Windows上的结果是非常令人期待的。因为共享库和可执行文件是分开构建的,它们应该知道counter::getcount中的静态变量。他们应该简单地为它分配内存,这就是为什么他们有不同的静态计数器。
我用NM、objdump等工具做了很多分析。虽然我不是他们的大专家,但我没有发现任何可疑之处。如果需要,我可以提供它们的输出。
使用ldd工具,我可以看到库在两种情况下都静态链接。
为什么我在Unix上看不到相同的结果,这很奇怪。根本原因可能在于构建选项(例如-fpic),还是我遗漏了什么?
在Windows中,除非添加
在Linux/Unix中,共享lib正在导出所有全局和静态符号,因此当链接器发现共享lib中存在静态成员时,它只使用其地址。
这就是产生不同结果的原因。
编辑:这是对答案的完全重写。更多细节。好的。
我认为这个问题应该得到更详细的回答。尤其是有些事情到目前为止还没有提到。好的。
依赖性助行器好的。
让我从"依赖性助行器"程序开始。好的。
它是一个很好的程序(尽管现在它的外观和感觉有点老了),它允许分析Windows二进制文件(包括exe和dll)中的符号,这些符号可以导出/导入以及它们自己对其他dll的依赖性。它还允许显示未修饰的符号名,但这似乎只适用于MSVC构建二进制文件。(还有一些,但这并不重要。)好的。
多亏了这个程序,关键信息(对于这个问题)可以被发现。所以我鼓励你在实验中使用它。好的。
在Linux和Windows上导出策略好的。
SHR已经指出了这一点,但为了完整的答案,我也会提到这一点。还有一些额外的细节。好的。
在Linux上,默认情况下,每个符号都从共享库中导出。另一方面,在Windows中,您必须显式地说明要从共享库中导出哪些符号。好的。
然而,GCC似乎提供了一些控制"windows风格"导出的方法。参见gcc wiki上的可见性条目示例。好的。
还要注意,在Linux和Windows上有各种导出方式。例如,通过向链接器提供用于导出符号的名称列表,这两种方法似乎都支持有选择地导出。但现在(至少在Windows上)这个应用似乎并不多。
什么可以导出?好的。
在一般性的介绍之后,我们现在继续讨论Windows案例。现在,您可以使用
但是声明不仅可以应用于函数、方法和全局变量。它也可以应用于类型。例如,您可以有:好的。
1 | class __declspec(dllexport) Counter { /* ... */ }; |
这种导出/导入通常意味着所有成员都可以导出/导入。好的。
不是那么容易!好的。
但那太容易了,不是吗?复杂的是,gcc和msvc处理导出类型的方式不同。好的。
我在这里的笔记主要基于实验(使用依赖性查询器进行的检查),所以我可能是错的或者不够精确。但我确实观察到了行为上的差异。好的。
在测试中,我使用了更新5的Express版本的MSVC 2013。对于gcc,我使用了nuwen.net版本13.0的mingw发行版。好的。
MSVC,当整个出口的出口型,每个和每一个成员。包括定义的成员(implicitly编译器生成的类的复制构造函数)。与包括内联函数。此外,如果有一些
GCC在其他的手似乎远更多的限制。它并不吨出口implicitly定义的成员。它不能出口或内联的成员。
出口/进口内联函数
如果不是你会明确的出口在整个出口型和内联函数,GCC出口只有真的想它。但它不会安静
如果你继续尝试更多的内联函数要导入在海湾合作委员会的错误。你不能定义一个海湾,你所有的符号。发生这种情况,当你进口和内联和符号的定义)。因此事实上它不让任何一个内联函数,GCC出口镰刀。
内联函数的使用允许进口。在所有情况下我发现它不似乎是内联函数,但不是所谓的"版本"。
然而,注意,使用内联函数的情况,因为其出口
(…) A static local variable in an extern inline function always refers to the same object. (…)
Ok.
但一个程序和一个共享库是如此他们是更多样的程序范围外的标准。然而,即使在使用案例的行为(或更好说:我会期望从一个法)为单程序。
解决方案
丹尼斯a comment趋中提供自己的解决方案的问题。解决方案是一个移动
这似乎是唯一的解决方案和使用便携式之间的海湾。或更确切的解决这个问题的使用允许更多的人希望可以在工作时,程序是编译的GCC。
变戏法。
上面的是不完全正确的。解决方法有一个持续的工作和实现之间的海湾。
这是
我的方式(或没有我不知道的任何直接的出口/进口)力
使用的解决方案
你有更多的选择和使用。
如前所述,导出/导入内联函数本身(直接或通过类型)将完成该任务。好的。
总结好的。
如上所述,即使在Windows上GCC和MSVC之间的一致性也只需要注意。你必须限制自己停留在允许解决方案的公共子集中。好的。
保持程序(源代码)在Linux和Windows之间的互操作性,即使使用相同的编译器(GCC),也需要小心。好的。
幸运的是,这三种环境都有一个通用的子集:Linux上的gcc、Windows上的gcc和Windows上的msvc。这一公共子集已经被提到的丹尼斯的评论所描述。好的。
因此,不要内联要导出/导入的函数。把它们放在源头上。在Windows版本上(不管编译器如何),显式导出它们(否则,由于在生成程序时共享库源中的函数将不可用,因此无论如何都会出现链接器错误)。好的。
请注意,这实际上是一种合理的方法。共享库中的内联函数似乎不明智。它不仅冻结接口,而且冻结(该函数的)实现。您不能再自由地更改此函数(并交付共享库的新版本),因为所有客户端都必须重新构建,因为它们可能已经内联了该函数。因此,不从共享库内联本身就是一种明智的方法。作为一个额外的好处,它可以确保您的资源是多平台友好型的。好的。
还可以查看gcc wiki上提到的可见性条目。在Linux上使用这种方法(显式导出)可能是合理的,因为它看起来更干净(从设计的角度来看)并且在运行时更高效。尽管它很适合你对窗户所做的一切。好的。好啊。