关于c ++:当使用静态成员与共享库到可执行文件的静态链接时,我在UNIX和WIN上收到不同的结果。

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中,除非添加dllexport语句,否则dll不会导出全局和静态符号,因此链接器甚至不知道它们存在,因此它为静态成员分配新的实例。

在Linux/Unix中,共享lib正在导出所有全局和静态符号,因此当链接器发现共享lib中存在静态成员时,它只使用其地址。

这就是产生不同结果的原因。


编辑:这是对答案的完全重写。更多细节。好的。

我认为这个问题应该得到更详细的回答。尤其是有些事情到目前为止还没有提到。好的。

依赖性助行器好的。

让我从"依赖性助行器"程序开始。好的。

它是一个很好的程序(尽管现在它的外观和感觉有点老了),它允许分析Windows二进制文件(包括exe和dll)中的符号,这些符号可以导出/导入以及它们自己对其他dll的依赖性。它还允许显示未修饰的符号名,但这似乎只适用于MSVC构建二进制文件。(还有一些,但这并不重要。)好的。

多亏了这个程序,关键信息(对于这个问题)可以被发现。所以我鼓励你在实验中使用它。好的。

在Linux和Windows上导出策略好的。

SHR已经指出了这一点,但为了完整的答案,我也会提到这一点。还有一些额外的细节。好的。

在Linux上,默认情况下,每个符号都从共享库中导出。另一方面,在Windows中,您必须显式地说明要从共享库中导出哪些符号。好的。

然而,GCC似乎提供了一些控制"windows风格"导出的方法。参见gcc wiki上的可见性条目示例。好的。

还要注意,在Linux和Windows上有各种导出方式。例如,通过向链接器提供用于导出符号的名称列表,这两种方法似乎都支持有选择地导出。但现在(至少在Windows上)这个应用似乎并不多。__declspec方法似乎更可取。好的。

什么可以导出?好的。

在一般性的介绍之后,我们现在继续讨论Windows案例。现在,您可以使用__declspec从共享库中导出/导入符号。如问题所示。(可能不完全是这样——通常您使用#define来处理双向性,如gcc wiki上已经提到的可见性条目所示。)好的。

但是声明不仅可以应用于函数、方法和全局变量。它也可以应用于类型。例如,您可以有:好的。

1
class __declspec(dllexport) Counter { /* ... */ };

这种导出/导入通常意味着所有成员都可以导出/导入。好的。

不是那么容易!好的。

但那太容易了,不是吗?复杂的是,gcc和msvc处理导出类型的方式不同。好的。

我在这里的笔记主要基于实验(使用依赖性查询器进行的检查),所以我可能是错的或者不够精确。但我确实观察到了行为上的差异。好的。

在测试中,我使用了更新5的Express版本的MSVC 2013。对于gcc,我使用了nuwen.net版本13.0的mingw发行版。好的。

MSVC,当整个出口的出口型,每个和每一个成员。包括定义的成员(implicitly编译器生成的类的复制构造函数)。与包括内联函数。此外,如果有一些static内联函数局部变量,他们出口到(get)。。。。。。。。

GCC在其他的手似乎远更多的限制。它并不吨出口implicitly定义的成员。它不能出口或内联的成员。

出口/进口内联函数

如果不是你会明确的出口在整个出口型和内联函数,GCC出口只有真的想它。但它不会安静static本地变量在这一函数的输出。

如果你继续尝试更多的内联函数要导入在海湾合作委员会的错误。你不能定义一个海湾,你所有的符号。发生这种情况,当你进口和内联和符号的定义)。因此事实上它不让任何一个内联函数,GCC出口镰刀。

内联函数的使用允许进口。在所有情况下我发现它不似乎是内联函数,但不是所谓的"版本"。

然而,注意,使用内联函数的情况,因为其出口static本地变量,所以它可能会真的为内联函数(它比进口,而同时保持a)单拷贝static本地变量。普通程序的搜索行为是由mandated(n3337 C++标准点(11),dcl.fct.spec 7.1.2 [ ])在4美元,我们可以读:

(…) A static local variable in an extern inline function always refers to the same object. (…)

Ok.

但一个程序和一个共享库是如此他们是更多样的程序范围外的标准。然而,即使在使用案例的行为(或更好说:我会期望从一个法)为单程序。

解决方案

丹尼斯a comment趋中提供自己的解决方案的问题。解决方案是一个移动getCount函数从源文件的头信息和进出口。

这似乎是唯一的解决方案和使用便携式之间的海湾。或更确切的解决这个问题的使用允许更多的人希望可以在工作时,程序是编译的GCC。

变戏法。

上面的是不完全正确的。解决方法有一个持续的工作和实现之间的海湾。

这是static停止使用局部变量。而不是让它的全局变量(最可能的变量在它的制作staticclass)和输出它。这将使诡计为好。

我的方式(或没有我不知道的任何直接的出口/进口)力static本地变量。你有改变他们做全局变量。

使用的解决方案

你有更多的选择和使用。

如前所述,导出/导入内联函数本身(直接或通过类型)将完成该任务。好的。

总结好的。

如上所述,即使在Windows上GCC和MSVC之间的一致性也只需要注意。你必须限制自己停留在允许解决方案的公共子集中。好的。

保持程序(源代码)在Linux和Windows之间的互操作性,即使使用相同的编译器(GCC),也需要小心。好的。

幸运的是,这三种环境都有一个通用的子集:Linux上的gcc、Windows上的gcc和Windows上的msvc。这一公共子集已经被提到的丹尼斯的评论所描述。好的。

因此,不要内联要导出/导入的函数。把它们放在源头上。在Windows版本上(不管编译器如何),显式导出它们(否则,由于在生成程序时共享库源中的函数将不可用,因此无论如何都会出现链接器错误)。好的。

请注意,这实际上是一种合理的方法。共享库中的内联函数似乎不明智。它不仅冻结接口,而且冻结(该函数的)实现。您不能再自由地更改此函数(并交付共享库的新版本),因为所有客户端都必须重新构建,因为它们可能已经内联了该函数。因此,不从共享库内联本身就是一种明智的方法。作为一个额外的好处,它可以确保您的资源是多平台友好型的。好的。

还可以查看gcc wiki上提到的可见性条目。在Linux上使用这种方法(显式导出)可能是合理的,因为它看起来更干净(从设计的角度来看)并且在运行时更高效。尽管它很适合你对窗户所做的一切。好的。好啊。