关于C ++:共享库中带有__attribute __((constructor))的全局/静态变量初始化问题

Global/Static variables initialization issue with __attribute__((constructor)) in shared library

我在共享库中使用__attribute__((constructor))初始化全局/静态变量时遇到一个问题,即某些变量似乎被初始化了两次。

以下是代码片段:

shared.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
struct MyStruct
{
  MyStruct(int s = 1)
  : s(s) {
    printf("%s, this: %p, s=%d
"
, __func__, this, s);
  }
  ~MyStruct() {
    printf("%s, this: %p, s=%d
"
, __func__, this, s);
  }
  int s;
};

MyStruct* s1 = nullptr;
std::unique_ptr<MyStruct> s2 = nullptr;
std::unique_ptr<MyStruct> s3;
MyStruct s4;

void onLoad() __attribute__((constructor));
void onLoad()
{
  s1 = new MyStruct;
  s2 = std::make_unique<MyStruct>();
  s3 = std::make_unique<MyStruct>();
  s4 = MyStruct(2);

  printf("&s1: %p, &s2: %p, &s3: %p
"
, &s1, &s2, &s3);
  printf("s1: %p, s2: %p, s3: %p
"
, s1, s2.get(), s3.get());
  printf("s4: %p, s4.s: %d
"
, &s4, s4.s);
}

extern"C" void foo()
{
  printf("&s1: %p, &s2: %p, &s3: %p
"
, &s1, &s2, &s3);
  printf("s1: %p, s2: %p, s3: %p
"
, s1, s2.get(), s3.get());
  printf("s4: %p, s4.s: %d
"
, &s4, s4.s);
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <cstdio>
#include <dlfcn.h>

using Foo = void(*)(void);

int main()
{
  printf("Calling dlopen...
"
);
  void* h = dlopen("./libshared.so", RTLD_NOW | RTLD_GLOBAL);
  Foo f = reinterpret_cast<Foo>(dlsym(h,"foo"));
  printf("
Calling foo()...
"
);
  f();
  return 0;
}

编译与

1
2
$ g++ -fPIC -shared -std=c++14 shared.cpp -o libshared.so
$ g++ -std=c++14 -o main main.cpp -ldl

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Calling dlopen...
MyStruct, this: 0x121b200, s=1
MyStruct, this: 0x121b220, s=1
MyStruct, this: 0x121b240, s=1
MyStruct, this: 0x7ffc19736910, s=2
~MyStruct, this: 0x7ffc19736910, s=2
&s1: 0x7fb1fe487190, &s2: 0x7fb1fe487198, &s3: 0x7fb1fe4871a0
s1: 0x121b200, s2: 0x121b220, s3: 0x121b240
s4: 0x7fb1fe4871a8, s4.s: 2
MyStruct, this: 0x7fb1fe4871a8, s=1

Calling foo()...
&s1: 0x7fb1fe487190, &s2: 0x7fb1fe487198, &s3: 0x7fb1fe4871a0
s1: 0x121b200, s2: (nil), s3: 0x121b240
s4: 0x7fb1fe4871a8, s4.s: 1
~MyStruct, this: 0x7fb1fe4871a8, s=1
~MyStruct, this: 0x121b240, s=1

预期s1s3的值。

但是s2s4的行为很奇怪。

  • s2.get()应该是0x121b220,但是在foo()中它变成nullptr
  • s4的值在onLoad()中显示为s4.s: 2,但是在调用其构造函数后,其默认值为s=1,然后在foo()中其值为s=1

将变量放入匿名名称空间具有相同的结果。

s2s4有什么问题?

我的操作系统:Ubuntu 16.04.2,GCC:5.4.0


根据有关此GCC错误报告和此后续文档补丁的讨论,您似乎看到的是GCC中未指定的行为(不是错误)。

However, the order in which constructors for C++ objects with static storage duration and functions decorated with attribute constructor are invoked is unspecified. In mixed declarations, attribute init_priority can be used to impose a specific ordering.

在这种情况下,似乎可以避免分段错误,因为分配给未初始化的std::unique_ptr可能导致未初始化的指针成员调用delete。 根据C ++规范,GCC的未指定行为会转换为未定义的行为(在这种情况下),因为从未初始化的变量中读取是未定义的行为(未初始化的unsigned char除外)。

无论如何,要纠正此问题,您确实确实需要使用__attribute((init_priority))来在构造函数之前命令初始化静态声明的对象。