2014-10-24 4 views
9

을 고려하십시오. 정적 변수 라이브러리 libA로 끝나는 컴파일 유닛에 정적 변수가 있습니다. 그런 다음이 변수에 액세스하는 다른 컴파일 단위가 libB.so 라이브러리 (libA는 libB에 링크되어야 함)로 공유됩니다. 마지막으로 나는 또한 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 

는 누군가에 무슨 일이 일어나고 있는지 설명해 주시겠습니까 이리? 링커는 무엇을하고 있습니까? 동일한 변수를 어떻게 두 번 초기화 할 수 있습니까?

+1

관련 (아마도 중복) : http://stackoverflow.com/questions/6714046/c-linux-double-destruction -of-static-variable-linking-symbols-overlap – jogojapan

+1

정적 라이브러리를 컴파일하고 공유 라이브러리를 컴파일하는 것과 관련 될 수 있습니까? 그래서 libB.so에 ClassA.o와 ClassB.o의 초기화 코드가 있습니까? – heksesang

+0

@heksesang : 예,이 별자리에서만 발생합니다. 만약 내가'A'와'B' 정적 라이브러리 또는 둘다 공유 라이브러리를 만들면, 나는이 문제에 직면하지 않는다 ('A'의 c'or는 한 번만 실행된다). 그러나 필자는 링커가 중복 된 기호 및 init 호출을 인식하고 제거 할 것으로 기대합니다. 내 가정이 잘못 되었습니까, 아니면 링커입니까? – bselu

답변

8

libA.alibB.so에 포함 시켰습니다. 이렇게하면 libB.solibA.a에 모두 ClassA.o이 포함되어 정적 멤버를 정의합니다. 지정한 링크 위해서는

링커는 너무 ClassA.o 초기화 코드 main() 전에 실행되고, 정적 라이브러리 libA.a에서 ClassA.o에서 가져옵니다.동적 인 libB.so의 첫 번째 기능에 액세스 할 때 libB.so의 모든 초기화 프로그램이 실행됩니다. libB.soClassA.o이 포함되어 있으므로 ClassA.o의 정적 초기화기를 다시 실행해야합니다.

가능한 수정 :

  1. 는 libA.a와 libB.so. 모두에 ClassA.o을 넣지 마십시오

    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 
+0

해결 방법 1 :'libA.a'를'libB.so'에 넣지 않으면, 정적 라이브러리에 대한 암묵적인 의존성을 갖는'libB.so'로 끝납니다. 따라서'libB.so'를 전달하고'libA.a'를 잊어 버리면 수신자는 해결되지 않은 기호를 얻게 될 것이고 불완전한 라이브러리를 전달했다고 생각해야합니다. 우리는 여기에 정상적인 도서관을 말할 수 있을까요? 수정 2에 대해서 :'Test.cpp'가'libB.so'에 의해 사용되지 않은'libA.a '내의 심볼을 참조한다면 이것이 작동하지 않을 것입니다. 'libA.a'를'libB.so'에 링크하면 링커는'libA.a'의 사용되지 않는 심볼을 버립니다. 그래서 나는 아직도'libA.a'에 대한 실행 파일을 링크해야합니다. – bselu

+0

libA.a를 libA.so로 전환하고 libA.so 및 libB.so를 모두 제공 할 수 있습니까? 그것은 아마도 가장 쉬운 해결책 일 것입니다. 더 어려운 해결책은 라이브러리를 리팩토링하는 것입니다. 각 오브젝트 파일은 최종 링크 단계에서 사용되는 하나의 라이브러리에만 존재해야합니다. –

6

누군가 여기서 무슨 일이 일어 났는지 설명해주세요.

복잡합니다. 주요 실행 파일에 하나, 그리고 libB.so 다른 :

첫째, 당신은 당신의 주요 실행 파일을 연결하는 방식과 공유 라이브러리는 staticA의 인스턴스 (및 ClassA.cpp에서 다른 모든 코드)에 존재하는됩니다.

당신은 그런 다음 두 개의 인스턴스에 대한 ClassA 생성자가 두 번 실행하는 것은 매우 놀라운 일이 아니다

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

를 실행하여 확인할 수 있습니다,하지만 this 포인터가 동일하다는 것을 여전히 놀랍다 (및 대응 주 실행 파일에서 staticA).

런타임 로더 (실패한 경우)는 아카이브 라이브러리와의 링크 동작을 에뮬레이션하려고 시도하고 staticA에 대한 모든 참조를 관찰하는 전역으로 내 보낸 첫 번째 인스턴스 (test의 인스턴스)에 바인딩하기 때문에 이러한 현상이 발생합니다.

그래서이 문제를 해결하려면 어떻게해야합니까? 그건 실제로 staticA에 달려 있습니다.

어떤 종류의 싱글 톤이라면 그 프로그램은 한 번만 존재해야합니다. 그렇다면 쉬운 해결책은 staticA이라는 단일 인스턴스 만 있도록하는 것입니다. 그리고이를 수행하는 방법은 libB.so을 사용하는 프로그램이 libA.a이 아니고이 아닌 링크 libB.so에 대해 libA.a을 연결하도록 요구하는 것입니다. 그러면 libB.so 안에 sttaicA의 인스턴스가 제거됩니다. 당신은 "libA는 libB에 링크되어야합니다"라고 주장했지만 그 주장은 거짓입니다. 당신이 libA.so 대신 libA.a의를 구축 할 경우

또는, 당신은 libB.solibA.so에 연결할 수 있습니다 (그래서 libB.so는 자체 포함)입니다. 기본 응용 프로그램이 libA.so에 대해서도 링크하는 경우에는 문제가되지 않습니다. 의 인스턴스가 libA.so 안에 있으며 해당 라이브러리가 몇 번이나 사용되었는지는 상관 없습니다.

반면에 staticA이 일종의 내부 구현 세부 사항을 나타내며 두 ​​인스턴스가 있으면 (서로 간섭하지 않는 한) 모두 해결책으로 표시됩니다. ClassA 기호는 숨김 상태로 표시되며 this answer과 같습니다.

업데이트 : 링커가 실행에서 staticA의 두 번째 인스턴스를 제거하지 않는 이유

.

링커에서 수행 한대로 수행합니다. 당신이 당신의 링크 명령 줄을 변경할 경우 :

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

다음 링커는 기본 실행 파일로 ClassA을 연결해서는 안된다. 명령 행에서 라이브러리의 순서가 중요한 이유를 이해하려면 this을 읽어보십시오.

+0

나는 링커가 실행 파일에서'staticA'의 두 번째 인스턴스를 제거하지 않는 이유는 아직 모르겠다. 이론적으로 이것은 가능해야합니다. – bselu

+0

@bselu 답변 됨. –

+0

알겠습니다. 업데이트 읽기 처음에는 링크 순서를 변경하면 일반적으로 내 문제가 해결 될 것이라고 생각했습니다. 그러나 그렇지 않습니다. 'staticA'에 접근하고'libA.a'에 링크 된 예제에 또 다른'libC.so'를 추가하면, 다시 c'tor가 두 번 호출됩니다. 필자가 볼 수있는 유일한 현명한 해결책은 libA.a를 libB.so에 연결하지 않는 것입니다. 그러나 libB.so에는 정적 lib에 대한 암시 적 (보이지 않는) 종속성이 있습니다. 허용됩니까? – bselu