考虑我在编译单元中有一个静态变量,它最终存在于静态库libA中。然后,我有另一个编译单元访问此变量,该变量最终位于共享库libB.so中(因此libA必须链接到libB)。最后我有一个main函数,也直接从A中访问静态变量和,它们依赖于libB(所以我链接libA 和 libB)。
然后观察,静态变量被初始化两次,即它的构造函数运行两次!这似乎不对。链接器是否应该将两个变量识别为相同并将它们优化为一个?
为了让我的混乱完美,我看到它使用相同的地址运行两次!也许链接器可以识别它,但没有删除static_initialization_and_destruction代码中的第二个调用?
这是一个展示:
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.hpp:
#ifndef CLASSB_HPP
#define CLASSB_HPP
class ClassB
{
public:
ClassB();
~ClassB();
void test();
};
#endif // CLASSB_HPP
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();
}
Test.cpp的:
#include <cstdio>
#include "ClassA.hpp"
#include "ClassB.hpp"
int main(int argc, char * argv[])
{
printf("main()\n");
ClassA::staticA.test();
ClassB b;
b.test();
printf("main: END\n");
return 0;
}
然后我按如下方式编译和链接:
g++ -c ClassA.cpp
ar rvs libA.a ClassA.o
g++ -c ClassB.cpp
g++ -shared -o libB.so ClassB.o libA.a
g++ -c Test.cpp
g++ -o test Test.cpp libA.a libB.so
输出是:
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
有人可以解释一下这里发生了什么吗?链接器在做什么?如何将相同的变量初始化两次?
答案 0 :(得分:9)
您将libA.a
加入libB.so
。通过这样做,libB.so
和libA.a
都包含ClassA.o
,它定义了静态成员。
在您指定的链接顺序中,链接器从静态库ClassA.o
中提取libA.a
,因此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.ag++ -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
答案 1 :(得分:7)
有人可以解释一下这里发生了什么吗?
这很复杂。
首先,您链接主可执行文件和共享库的方式导致两个实例staticA
(以及来自ClassA.cpp
的所有其他代码)出现:一个在主可执行文件中,另一个在libB.so
中。
您可以通过
确认nm -AD ./test ./libB.so | grep staticA
这两个实例的ClassA
构造函数运行两次并不奇怪,但this
指针相同(并且对应staticA
仍然令人惊讶在主要的可执行文件中)。
这种情况正在发生,因为运行时加载器(失败)尝试模拟与归档库链接的行为,并将对staticA
的所有引用绑定到它观察到的第一个全局导出的实例({{1 }})。
那么你能做些什么来解决这个问题呢?这取决于test
实际代表的内容。
如果它是某种单例,它应该只在任何程序中存在一次,那么简单的解决方案是使它只有staticA
的单个实例。这样做的一种方法是要求使用staticA
的任何程序也针对libB.so
链接,而不链接libA.a
针对libB.so
。这将消除libA.a
内sttaicA
的实例。你声称“libA必须链接到libB”,但这种说法是错误的。
或者,如果您构建的是libB.so
而不是libA.so
,那么您可以将libA.a
与libB.so
相关联(因此libA.so
是自包含的)。如果主应用程序也链接到libB.so
,那就不会有问题:libA.so
内只有一个staticA
实例,而不管该库使用了多少次。
另一方面,如果libA.so
表示某种内部实现细节,并且您可以使用它的两个实例(只要它们不互相干扰),那么解决方案是标记隐藏可见性的所有staticA
符号,如this answer所示。
<强>更新强>
为什么链接器不会从可执行文件中删除第二个staticA实例。
因为链接器执行了您告诉它的操作。如果您将链接命令行更改为:
ClassA
然后链接器不应将g++ -o test Test.cpp libB.so libA.a
链接到主可执行文件中。要理解为什么命令行中的库顺序很重要,请阅读this。