bse*_*elu 11 c++ linker initialization static-members
考虑我在编译单元中有一个静态变量,它最终存在于静态库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
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
ClassB.hpp:
#ifndef CLASSB_HPP
#define CLASSB_HPP
class ClassB
{
public:
ClassB();
~ClassB();
void test();
};
#endif // CLASSB_HPP
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
我然后编译和链接如下:
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
Run Code Online (Sandbox Code Playgroud)
输出是:
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
Run Code Online (Sandbox Code Playgroud)
有人可以解释一下这里发生了什么吗?链接器在做什么?如何将同一变量初始化两次?
你libA.a加入了libB.so.通过这样做,既libB.so和libA.a包含ClassA.o,它定义了静态成员.
在您指定的链接顺序中,链接器ClassA.o从静态库中拉入libA.a,因此ClassA.o之前运行初始化代码main().libB.so访问动态中的第一个函数时,将运行所有初始值设定项libB.so.由于libB.soinclude ClassA.o,ClassA.o必须再次运行静态初始化程序.
可能的修复:
不要将ClassA.o放入libA.a和libB.so.
g++ -shared -o libB.so ClassB.o
Run Code Online (Sandbox Code Playgroud)不要使用这两个库; 不需要libA.a.
g++ -o test Test.cpp libB.so
Run Code Online (Sandbox Code Playgroud)应用上述任一方法可解决问题:
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
Run Code Online (Sandbox Code Playgroud)
有人可以解释一下这里发生了什么吗?
情况很复杂.
首先,链接主可执行文件和共享库的方式会导致两个实例staticA(以及所有其他代码ClassA.cpp)出现:一个在主可执行文件中,另一个在libB.so.
您可以通过运行来确认
nm -AD ./test ./libB.so | grep staticA
Run Code Online (Sandbox Code Playgroud)
然后ClassA,两个实例的构造函数运行两次并不奇怪,但this指针是相同的(并且对应staticA于主可执行文件)仍然令人惊讶.
之所以发生这种情况,是因为运行时加载程序(失败)尝试模拟与归档库链接的行为,并将所有引用绑定到staticA它观察到的第一个全局导出的实例(其中的一个test).
那么你能做些什么来解决这个问题呢?这取决于staticA实际代表什么.
如果它是某种单例,它应该只在任何程序中存在一次,那么简单的解决方案是使它只有一个实例staticA.而一个办法做到这一点是要求使用任何程序libB.so也链接反对libA.a,而不是链接libB.so反对libA.a.这将消除sttaicA内部的实例libB.so.您声称"libA必须链接到libB",但该声明是错误的.
另外,如果你建立libA.so,而不是libA.a,那么你就可以链接libB.so反对libA.so(所以libB.so是自包含的).如果主应用程序也链接libA.so,那就不会有问题:staticA内部只有一个实例libA.so,而不管该库使用了多少次.
另一方面,如果staticA表示某种内部实现细节,并且您可以使用它的两个实例(只要它们不互相干扰),那么解决方案是标记ClassA具有隐藏可见性的所有符号,正如这个答案所示.
更新:
为什么链接器不会从可执行文件中删除staticA的第二个实例.
因为链接器执行了您告诉它的操作.如果将链接命令行更改为:
g++ -o test Test.cpp libB.so libA.a
Run Code Online (Sandbox Code Playgroud)
然后链接器不应链接ClassA到主可执行文件.要理解为什么命令行上的库顺序很重要,请阅读此内容.
| 归档时间: |
|
| 查看次数: |
2805 次 |
| 最近记录: |