C++ 静态变量初始化两次

C++ 静态变量初始化两次,c++,linker,initialization,static-members,C++,Linker,Initialization,Static Members,假设我在编译单元中有一个静态变量,它最终位于静态库libA中。然后,我有另一个编译单元访问这个变量,它最终位于一个共享库libB.so中(因此libA必须链接到libB)。最后,我有一个main函数,它也直接从访问静态变量,并且依赖于libB(因此我链接libA和libB) 然后我观察到,静态变量初始化了两次,即它的构造函数运行了两次!这似乎不对。链接器不应该识别两个变量是相同的,并将它们作为一个进行优化吗 为了使我的困惑更加完美,我看到它使用相同的地址运行了两次!因此,也许链接器确实识别了它,

假设我在编译单元中有一个静态变量,它最终位于静态库libA中。然后,我有另一个编译单元访问这个变量,它最终位于一个共享库libB.so中(因此libA必须链接到libB)。最后,我有一个main函数,它也直接从访问静态变量,并且依赖于libB(因此我链接libA和libB)

然后我观察到,静态变量初始化了两次,即它的构造函数运行了两次!这似乎不对。链接器不应该识别两个变量是相同的,并将它们作为一个进行优化吗

为了使我的困惑更加完美,我看到它使用相同的地址运行了两次!因此,也许链接器确实识别了它,但没有删除静态\u初始化\u和\u销毁代码中的第二个调用

这是一个展示:

ClassA.hpp:

#ifndef CLASSA_HPP
#define CLASSA_HPP

class ClassA
{
public:
    ClassA();
    ~ClassA();
    static ClassA staticA;

    void test();
};

#endif // CLASSA_HPP
ClassA.cpp:

#include <cstdio>
#include "ClassA.hpp"

ClassA ClassA::staticA;

ClassA::ClassA()
{
    printf("ClassA::ClassA() this=%p\n", this);
}

ClassA::~ClassA()
{
    printf("ClassA::~ClassA() this=%p\n", this);
}

void ClassA::test()
{
    printf("ClassA::test() this=%p\n", this);
}
ClassB.cpp:

 #include <cstdio>
 #include "ClassA.hpp"
 #include "ClassB.hpp"

 ClassB::ClassB()
 {
     printf("ClassB::ClassB() this=%p\n", this);
 }

 ClassB::~ClassB()
 {
     printf("ClassB::~ClassB() this=%p\n", this);
 }

 void ClassB::test()
 {
     printf("ClassB::test() this=%p\n", this);
     printf("ClassB::test: call staticA.test()\n");
     ClassA::staticA.test();
 }
输出为:

ClassA::ClassA() this=0x804a040
ClassA::ClassA() this=0x804a040
main()
ClassA::test() this=0x804a040
ClassB::ClassB() this=0xbfcb064f
ClassB::test() this=0xbfcb064f
ClassB::test: call staticA.test()
ClassA::test() this=0x804a040
main: END
ClassB::~ClassB() this=0xbfcb064f
ClassA::~ClassA() this=0x804a040
ClassA::~ClassA() this=0x804a040
有人能解释一下这是怎么回事吗?链接器在做什么?同一个变量如何初始化两次

有人能解释一下这是怎么回事吗

这很复杂

首先,链接主可执行文件和共享库的方式导致出现两个
staticA
(以及
ClassA.cpp
中的所有其他代码):一个在主可执行文件中,另一个在
libB.so

您可以通过运行

nm -AD ./test ./libB.so | grep staticA
因此,这两个实例的
ClassA
构造函数运行两次并不奇怪,但是
这个
指针是相同的(并且对应于主可执行文件中的
staticA

这是因为运行时加载程序(未成功)尝试模拟与存档库链接的行为,并将对
staticA
的所有引用绑定到它观察到的第一个全局导出实例(test中的实例)

那么你能做些什么来解决这个问题呢?这取决于staticA实际代表的内容

如果它是某种单例,在任何程序中应该只存在一次,那么简单的解决方案是使它只存在一个
staticA
的实例。一种方法是要求任何使用
libB.so
的程序也链接到
libA.a
,而不是链接
libB.so
libA.a
。这将消除
libB.so中的
sttaicA
实例。您声称“libA必须链接到libB”,但这一说法是错误的

或者,如果您构建
libA.so
而不是
libA.a
,那么您可以将
libB.so
链接到
libA.so
(so
libB.so
是自包含的)。如果主应用程序也链接到
libA.so
,那就不会有问题:在
libA.so
中只有一个
staticA
实例,而不管该库被使用了多少次

另一方面,如果
staticA
表示某种内部实现细节,并且您可以使用它的两个实例(只要它们不相互干扰),那么解决方案是使用隐藏可见性标记所有
ClassA
符号,如建议的那样

更新:

为什么链接器不从可执行文件中删除staticA的第二个实例

因为链接器会按照你的要求去做。如果将链接命令行更改为:

g++ -o test Test.cpp libB.so libA.a

然后链接器不应该将
ClassA
链接到主可执行文件中。要理解为什么命令行上的库顺序很重要,请阅读。

您将
libA.a
包含到
libB.so
中。通过这样做,
libB.so
libA.a
都包含定义静态成员的
ClassA.o

按照您指定的链接顺序,链接器从静态库
libA.a
中拉入
ClassA.o
,因此
ClassA.o
初始化代码在
main()
之前运行。当动态
libB.so
中的第一个函数被访问时,所有
libB.so
的初始值设定项都将运行。由于
libB.so
包括
ClassA.o
ClassA.o
的静态初始值设定项必须(再次)运行

可能的修复方法:

  • 不要将ClassA.o同时放入libA.a和libB.so中

    g++ -shared -o libB.so ClassB.o
    
  • 不要同时使用两个库;不需要libA.a

    g++ -o test Test.cpp libB.so
    
  • 应用上述任一方法可修复此问题:

    ClassA::ClassA() this=0x600e58
    main()
    ClassA::test() this=0x600e58
    ClassB::ClassB() this=0x7fff1a69f0cf
    ClassB::test() this=0x7fff1a69f0cf
    ClassB::test: call staticA.test()
    ClassA::test() this=0x600e58
    main: END
    ClassB::~ClassB() this=0x7fff1a69f0cf
    ClassA::~ClassA() this=0x600e58
    

    相关(可能重复):它是否与编译静态库,然后使用它编译共享库有关?所以libB中有ClassA.o和ClassB.o的init代码。那么?@heksesang:是的,它只发生在这个星座中。如果我同时使用
    A
    B
    静态lib或两个共享lib,我就不会面临这个问题(
    A
    的任务表只运行一次)。但是,我希望链接器能够识别并消除重复符号和初始化调用。我的假设是错误的还是链接器?我仍然不明白,为什么链接器不从可执行文件中删除第二个实例
    staticA
    。理论上这应该是可能的。好吧,我明白了。阅读你的更新,我首先想到改变链接顺序可能会解决我的问题。然而,事实并非如此。如果我将另一个
    libC.so
    添加到示例中,同时访问
    staticA
    并链接到
    libA.a
    ,则再次调用c'tor两次。我看到的唯一明智的解决方案是不将libA.a链接到libB.so。但是libB.so对静态库有一个隐式(不可见)依赖关系。这是允许的吗?关于修正1:如果我不将
    libA.a
    放入
    libB.so
    ,我最终会得到
    libB.so
    对静态库的隐式依赖。所以如果我交付libB.So
    并忘记
    g++ -shared -o libB.so ClassB.o
    
    g++ -o test Test.cpp libB.so
    
    ClassA::ClassA() this=0x600e58
    main()
    ClassA::test() this=0x600e58
    ClassB::ClassB() this=0x7fff1a69f0cf
    ClassB::test() this=0x7fff1a69f0cf
    ClassB::test: call staticA.test()
    ClassA::test() this=0x600e58
    main: END
    ClassB::~ClassB() this=0x7fff1a69f0cf
    ClassA::~ClassA() this=0x600e58