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