2017-05-12 2 views
1

共有ライブラリ内のグローバル/静的変数の初期化に関する問題が発生しました。特定の変数が2回初期化されているようです。共有ライブラリの__attribute __((コンストラクタ))によるグローバル/静的変数の初期化の問題

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); 
} 

main.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に問題がありますか?

マイOS:Ubuntuの16.04.2、GCC:5.4.0

+0

「2回初期化されました」という証拠はありません –

+1

ctor/dtor呼び出しのシーケンスは、 'onLoad()'が呼び出される前に 's4'が構築されなかったように見せますが、その後に構築されます。最初の3つのctorコールは、ヒープ割り当てのものです。 4番目は一時的な 'MyStruct(2)'であり、次のdtor呼び出しは一時的に破棄されます。最後の 'printf()'の後まで、 's4'のデフォルトのctor呼び出しはありません。それは確かに奇妙です。しかし、それはおそらく 's4.s 'が1になる理由です。 – cdhowie

+0

@BoundaryImpositionええ、そういうわけで、私はそれが*そうだと言います。たとえば、 's4'は2回構築されているようですが、構築前に使用されていて、それが構築されています。 – Mine

答えて

2

何を見ていることはGCC(ないバグ)で、不特定の行動であると思わthis GCC bug reportthis follow-up doc patchについての議論を1として。

しかし、属性constructor飾ら静的記憶域期間と機能を持つC++オブジェクトが呼び出されるのコンストラクタ順序が指定されていません。複合宣言では、属性init_priorityを使用して特定の順序付けを行うことができます。

それは初期化されていないstd::unique_ptrに割り当てるようdeleteが初期化されていないポインタメンバーのために呼び出さされる可能性があり、セグメンテーション違反が狭く回避された。この場合には思えます。 GCCの未定義の動作は、undefined behavior to read from an uninitialized variable(初期化されていないunsigned charを除く)であるため、C++仕様に従って未定義の動作(この特定の場合)に変換されます。

とにかく、この問題を解決するには、実際には__attribute((init_priority))を使用して、コンストラクタ関数の前に静的に宣言されたオブジェクトの初期化を順序付ける必要があります。

+0

これはまだGCCバグとみなされています。バグはまだ閉じていないからです。 '__attribute __((init_priority(101)))')を使うと問題が解決されます。 – Mine

関連する問題