2017-05-12 4 views
1

공유 라이브러리에서 __attribute__((constructor))으로 전역/정적 변수를 초기화 할 때 특정 변수가 두 번 초기화되는 것으로 보이는 문제가 발생합니다.공유 라이브러리에서 __attribute __ ((constructor))와의 전역/정적 변수 초기화 문제

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 
컴파일 : 아래

는 코드 조각입니다

출력 : s1s3의 값은 예상되는

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 

.

그러나 s2s4은 이상한 행동을 보입니다.

  • s2.get()하면 0x121b220 수 있지만 foo()에서 그것은 nullptr된다한다;
  • s4의 값은 onLoad()s4.s: 2로 인쇄되어 있지만, 생성자가 기본 값 s=1 불려 그 후, 다음에 foo() 그 값은 s=1입니다.

변수를 익명 네임 스페이스에 넣는 것은 동일한 결과를 가져옵니다.

s2s4의 문제점은 무엇입니까?

내 OS : 우분투 16.04.2, GCC : 5.4.0

+0

아무 것도 "초기화되지 않았습니다"라는 두 가지 증거가 보이지 않습니다 –

+1

ctor/dtor 호출 시퀀스는'onLoad()'가 호출되기 전에's4'가 생성 된 적이없는 것처럼 보이지만 이후에 생성됩니다. 처음 세 개의 ctor 호출은 힙 할당에서 가져온 것입니다. 네 번째는 임시 MyStruct (2)이고 다음의 dtor 호출은 일시적으로 삭제됩니다. 's4'에 대한 기본 ctor 호출은 없으며 마지막'printf()'까지입니다. 참 이상합니다. 그것은 아마도 s4.s가 1이되는 이유 일 것입니다. – cdhowie

+0

@BoundaryImposition 그래, 그래서 내가 말하는 것 같아. * 난 잘 모르겠다. 예를 들어's4'는 두 번 구성되어 있거나 구성하기 전에 사용 된 것입니다. 그러면 구성됩니다. – Mine

답변

2
this GCC bug report에 대한 논의 사항에 따라

this follow-up doc patch 당신이보고있는 것은 GCC에 지정되지 않은 행동 (안 버그) 인 것 같다.

그러나, 주문 된 호출 속성 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