静态变量初始化两次

问题描述 投票:11回答:2

考虑我在编译单元中有一个静态变量,它最终存在于静态库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

有人可以解释一下这里发生了什么吗?链接器在做什么?如何将同一变量初始化两次?

c++ linker initialization static-members
2个回答
9
投票

你将libA.a包括在libB.so中。通过这样做,libB.solibA.a都包含ClassA.o,它定义了静态成员。

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

可能的修复:

  1. 不要将ClassA.o放入libA.a和libB.so. g++ -shared -o libB.so ClassB.o
  2. 不要使用这两个库;不需要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

7
投票

有人可以解释一下这里发生了什么吗?

情况很复杂。

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

您可以通过运行来确认

nm -AD ./test ./libB.so | grep staticA

然后,两个实例的ClassA构造函数运行两次并不奇怪,但this指针是相同的(并且对应于主可执行文件中的staticA)仍然令人惊讶。

之所以发生这种情况,是因为运行时加载程序(失败)尝试模拟与归档库链接的行为,并将对staticA的所有引用绑定到它观察到的第一个全局导出的实例(test中的实例)。

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

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

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

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

更新:

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

因为链接器执行了您告诉它的操作。如果将链接命令行更改为:

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

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

© www.soinside.com 2019 - 2024. All rights reserved.