2013-02-14 2 views
6

이제는 비 리프 클래스에 새로운 가상 함수를 추가하는 것이 좋지 않은 것으로 알고 있습니다. 다시 컴파일되지 않은 파생 클래스에 대한 이진 호환성을 손상시킵니다.순수한 가상 함수 및 이진 호환성

내가 예를 들어, 공유 라이브러리로 컴파일 된 인터페이스 클래스와 구현 클래스가 : 그러나, 나는 약간 다른 상황이

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     ... 
} 

class Impl { 
    public: 
     ... 
     void Foo(uint16_t arg); 
     .... 
} 

내 주요 응용 프로그램이 공유 라이브러리를 사용을, 기본적으로 기록 될 수있다 로 : 즉

Interface* foo = Implementation::giveMeImplPtr(); 
foo->Foo(0xff); 

는 응용 프로그램이 Interface에서 파생 된 모든 클래스가없는, 그것은 단지 그것을 사용합니다.

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     virtual void Foo(uint32_t arg) = 0; 
     ... 
} 

을하고 응용 프로그램을 다시 컴파일 할 필요없이 내 공유 라이브러리를 컴파일 : 이제

은, 내가 할 안전하다, 내가 Foo(uint32_t arg)Foo(uint16_t arg)에 과부하가하고 싶은 말은?

그렇다면 알고 있어야하는 특이한 경고가 있습니까? 그렇지 않은 경우 히트 및 라이브러리의 최신 버전을 가져 오는 이외의 다른 옵션이 있습니까?

답변

5

ABI는 기본적으로 vtable을 포함하여 개체의 크기와 모양에 따라 다릅니다. 가상 함수를 추가하면 vtable이 확실히 변경되고 컴파일러에 따라 변경됩니다.

이 경우 다른 점은 ABI 변경을 제안하는 것이 아니라 컴파일 타임에 감지하기 어려운 API를 깨는 것입니다. 이러한 가상 기능하지 않았다 및 ABI 호환성 문제가 아니었다 경우, 변경 후, 뭔가 같은 :

void f(Interface * i) { 
    i->Foo(1) 
} 

조용히 새 함수를 호출 끝날 것입니다,하지만 코드를 컴파일하는 경우에만, 디버깅을 할 수있는 매우 어렵다.

5

간단한 대답은 다음과 같습니다. 클래스 의 정의를 으로 변경할 때마다이 잠재적으로 이진 호환성을 잃을 수 있습니다. 비 가상 함수 또는 정적 멤버를 추가하는 것은 일반적으로 실제로는 안전하지만 이지만 공식적으로 정의되지 않은 동작이지만 입니다. 다른 것들은 바이너리 호환성을 깨뜨릴 것입니다.

2

내가 비슷한 상황에 있었을 때 MSVC 이 (가) 과부하 기능의 순서를 뒤집어 놓았을 때 매우 놀라웠습니다. 귀하의 예에 따르면, MSVC는이 같은 (이진)이 v_table 구성합니다 :

class Interface { 
    virtual void first() = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void final() = 0; 
} 
이 가

MSVC는 다음 v_table을 구성합니다 :

virtual void Foo(uint32_t arg) = 0; 
virtual void Foo(uint16_t arg) = 0; 

을 우리는 다음과 같이 귀하의 예를 조금 넓힐 수 있습니다 경우 :

virtual void first() = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void final() = 0; 

볼랜드 빌더 및 GCC 순서를 변경하지 않지만,

    ,
  1. 그들은하지이, 그것은 서사시 최종

실패 할 것 내가 라이브러리 (예) GCC로 컴파일 된 경우

  • 을 테스트 및 응용 프로그램이 MSVC로 컴파일됩니다 버전 있다는 것을 바이너리 호환성에 의존하지 마십시오. 클래스를 변경하면 모든 코드를 다시 컴파일해야합니다.

  • 2

    당신은 인기 을 설명하기 위해 노력하고있다 "클래스를 확인 이외의 유도"를 심비안 C++ API를 (을 찾아 NewL 팩토리 메소드)에, 예를 들어, 사용되는 바이너리 호환성을 보존 기술 :

    1. 팩토리 기능을 제공하십시오.
    2. 는 C++ 생성자 개인 (비 수출 인라인이 아닌, 그리고 클래스가 친구 클래스 또는 함수를해서는 안) 선언이 추론 비 클래스가 만들어 다음을 수행 할 수 있습니다

      • 가상 추가
      • 클래스 멤버가 추가되고 클래스 크기가 변경됩니다. 이 바이너리 수준에서 가상 함수의 소스 순서를 저장하기 때문에

    이 기술은 GCC 컴파일러 작동합니다.

    설명

    가상 기능이 아닌 변환 된 이름으로 객체의 V-테이블 오프셋에 의해 호출된다. 정적 팩토리 메서드를 호출하여 개체 포인터 만 가져올 수 있고 모든 가상 함수의 오프셋을 보존하면 (소스 순서를 저장하고 마지막에 새 메서드를 추가하여)이 경우 이전 버전과 호환 될 수 있습니다.

    클래스가 public 생성자 (인라인 또는 인라인이 아닌)이있는 경우 호환성이 깨진 것

    :

    • 인라인 : 응용 프로그램이 클래스의 기존 V-테이블과 기존 메모리 레이아웃을 복사됩니다 이는 새로운 도서관에서 사용 된 것과는 다릅니다. 내 보낸 메서드를 호출하거나 해당 메서드에 인수로 개체를 전달하면 세그먼트 화 오류의 메모리 손상이 발생할 수 있습니다.

    • 인라인이 아닌 : 링커에서 파생 된 클래스의 V-테이블 레이아웃을 재배치하기 때문에 당신이, 잎 클래스 선언의 끝에 새로운 가상 메소드를 추가하여 V-테이블을 변경할 수 있기 때문에 상황이 더 낫다 새 라이브러리 버전을로드하면 클라이언트 측에서; 컴파일 타임에 크기가 하드 코딩되고 새 버전 생성자를 호출하면 클라이언트 스택 또는 힙에서 인접한 객체의 메모리가 손상 될 수 있기 때문에 클래스 크기를 변경할 수 없습니다 (즉, 새 필드 추가).

    도구

    봅니다 리눅스에서 클래스 라이브러리 버전의 이전 버전과 바이너리 호환성을 확인하기 위해 abi-compliance-checker 도구를 사용합니다.