【问题标题】:Global/Static variables initialization issue with __attribute__((constructor)) in shared library共享库中 __attribute__((constructor)) 的全局/静态变量初始化问题
【发布时间】:2017-05-12 15:15:08
【问题描述】:

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

下面是代码sn-ps:

shared.cpp

struct MyStruct
{
  MyStruct(int s = 1)
  : s(s) {
    printf("%s, this: %p, s=%d\n", __func__, this, s);
  }
  ~MyStruct() {
    printf("%s, this: %p, s=%d\n", __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\n", &s1, &s2, &s3);
  printf("s1: %p, s2: %p, s3: %p\n", s1, s2.get(), s3.get());
  printf("s4: %p, s4.s: %d\n", &s4, s4.s);
}

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

ma​​in.cpp

#include <cstdio>
#include <dlfcn.h>

using Foo = void(*)(void);

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

编译

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

输出:

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

【问题讨论】:

  • 我没有看到任何证据表明任何东西都“初始化了两次”
  • ctor/dtor 调用的顺序使得s4 似乎在onLoad() 被调用之前从未被构造,但在之后被构造。前三个 ctor 调用来自您的堆分配。第四个是临时的MyStruct(2),下面的 dtor 调用是被销毁的临时。 s4 没有默认的 ctor 调用——直到最后的 printf() 之后。这确实很奇怪。不过,这可能就是 s4.s 变为 1 的原因。
  • @BoundaryImposition 是的,这就是我说它的原因似乎,我只是不确定。比如s4好像被构造了两次,或者构造之前使用,然后构造...
  • 不,它没有。输出准确地显示了对 MyStruct 构造函数的正确调用次数。
  • @BoundaryImposition 确实如此,但顺序不正确。我们在没有-O 的情况下进行编译,所以我们不会得到复制省略。看我的评论。 s4 确实似乎是在其默认构造之前从临时复制分配的,这发生在 onLoad() 返回之后,用 1 破坏值 2。

标签: c++ c++11 shared-libraries


【解决方案1】:

根据关于 this GCC bug reportthis follow-up doc patch 的讨论,您所看到的似乎是 GCC 中未指定的行为(不是错误)。

但是,具有静态存储持续时间的 C++ 对象的构造函数和使用属性 constructor 修饰的函数的调用顺序未指定。在混合声明中,init_priority 属性可用于强加特定的顺序。

在这种情况下,似乎勉强避免了段错误,因为分配给未初始化的 std::unique_ptr 可能会导致为未初始化的指针成员调用 delete。根据 C++ 规范,GCC 的未指定行为转换为未定义行为(在此特定情况下),因为它是 undefined behavior to read from an uninitialized variable(除了未初始化的 unsigned char)。

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

【讨论】:

  • 我仍然认为这是 GCC 错误,因为该错误仍未关闭。使用 __attribute__((init_priority(101))) 确实可以解决问题。
猜你喜欢
  • 2012-01-16
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多