C ++에서 가상 기본 클래스는 무엇입니까?


403

" 가상 기본 클래스 "가 무엇이며 그 의미가 무엇인지 알고 싶습니다 .

예를 보여 드리겠습니다.

class Foo
{
public:
    void DoSomething() { /* ... */ }
};

class Bar : public virtual Foo
{
public:
    void DoSpecific() { /* ... */ }
};

클래스 A에 멤버 변수 int a가 있고 클래스 B에도 멤버 int a가 있고 클래스 c가 클래스 A와 B를 상속하는 경우 '다중 상속'에서 가상 기본 클래스를 사용해야합니까?
Namit Sinha

2
@NamitSinha 아니오, 가상 상속은 그 문제를 해결 하지 못합니다 . 멤버 a는 어쨌든 모호 할 것입니다
Ichthyo

@NamitSinha 가상 상속은 여러 상속 관련 모호성을 제거하는 마법의 도구가 아닙니다. 간접 기지를 두 번 이상 갖는 "문제"를 "해결"합니다. 공유하려는 경우에만 문제가됩니다 (종종 항상 그런 것은 아닙니다).
curiousguy

답변:


533

가상 상속에 사용되는 가상 기본 클래스는 여러 상속을 사용할 때 주어진 클래스의 여러 "인스턴스"가 상속 계층 구조에 나타나지 않도록하는 방법입니다.

다음 시나리오를 고려하십시오.

class A { public: void Foo() {} };
class B : public A {};
class C : public A {};
class D : public B, public C {};

위의 계층 구조는 다음과 같은 "두려운 다이아몬드"를 만듭니다.

  A
 / \
B   C
 \ /
  D

D의 인스턴스는 A를 포함하는 B와 A를 포함하는 C로 구성됩니다. 따라서 A의 더 나은 표현을 위해 두 가지 "인스턴스"가 있습니다.

이 시나리오가 있으면 모호 할 가능성이 있습니다. 이렇게하면 어떻게됩니까?

D d;
d.Foo(); // is this B's Foo() or C's Foo() ??

이 문제를 해결하기 위해 가상 상속이 있습니다. 클래스를 상속 할 때 virtual을 지정하면 컴파일러에게 단일 인스턴스 만 원한다고 알려줍니다.

class A { public: void Foo() {} };
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};

이는 계층 구조에 A의 "인스턴스"가 하나만 포함되어 있음을 의미합니다. 그 후

D d;
d.Foo(); // no longer ambiguous

이것은 간단한 요약입니다. 자세한 내용은 thisthis를 읽으십시오 . 여기 에서도 좋은 예가 있습니다 .


7
@Bohdan 아니오 :)
OJ.

6
@OJ. 왜 안돼? 그들은 재미있다 :)
Bohdan

15
@Bohdan은 가상 키워드를 적게 사용합니다. 가상 키워드를 사용할 때는 무거운 메커니즘이 적용되기 때문입니다. 따라서 프로그램 효율성이 떨어집니다.
Sagar

73
"두려운 다이아몬드"다이어그램은 일반적으로 사용되는 것처럼 보이지만 혼란 스럽습니다. 이것은 실제로 객체 레이아웃이 아닌 클래스 상속 관계를 보여주는 다이어그램 입니다. 혼란스러운 부분은을 사용 virtual하면 객체 레이아웃이 다이아몬드처럼 보입니다. 우리가 사용하지 않으면 virtual객체 레이아웃은 두 개의 As 를 포함하는 트리 구조처럼 보입니다
MM

5
MM에 의해 설명 된 이유로이 답변을 하향 투표해야합니다. 다이어그램은 게시물의 반대를 나타냅니다.
David Stone

251

메모리 레이아웃

참고로, Dreaded Diamond의 문제점은 기본 클래스가 여러 번 존재한다는 것입니다. 따라서 정기적으로 상속하면 다음이 있다고 생각합니다.

  A
 / \
B   C
 \ /
  D

그러나 메모리 레이아웃에는 다음이 있습니다.

A   A
|   |
B   C
 \ /
  D

호출 할 때 D::foo()모호한 문제가 발생 하는 이유를 설명 합니다. 그러나의 실제 변수는의 멤버 변수를 사용하려고 할 때 발생합니다 A. 예를 들어 다음과 같이 가정 해 봅시다.

class A
{
    public :
       foo() ;
       int m_iValue ;
} ;

에서 액세스 m_iValue를 시도 D하면 계층 구조에서 m_iValue하나가 아닌 두 개가 표시되므로 컴파일러가 항의 합니다. 그리고 하나를 수정하면 B::m_iValue(즉 , 의 A::m_iValue부모 B) C::m_iValue수정되지 않습니다 (즉 A::m_iValue,C ).

여기에서 가상 상속이 편리해집니다. 단 하나의 foo()메소드뿐만 아니라 하나의 유일한 다이아몬드 레이아웃으로 진정한 다이아몬드 레이아웃으로 돌아갑니다 m_iValue.

무엇이 잘못 될 수 있습니까?

상상해보십시오.

  • A 몇 가지 기본 기능이 있습니다.
  • B 그것에 일종의 멋진 데이터 배열을 추가합니다 (예 :)
  • C옵저버 패턴과 같은 멋진 기능을 추가합니다 (예 : on m_iValue).
  • DBC에서 상속받습니다 A.

일반 상속으로 수정 m_iValue에서 것은 D모호하며이 해결해야합니다. 있어도 m_iValues안에 2 개가 있습니다D 있으므로이를 기억하고 동시에 두 개를 업데이트하는 것이 좋습니다.

가상 상속으로 수정 m_iValue에서 것은 D당신이 가지고 ...하자 말 괜찮습니다 ...하지만 D. C인터페이스를 통해 관찰자를 첨부했습니다. B인터페이스 를 통해 멋진 배열을 업데이트하면 직접 변경하는 부작용이 있습니다 m_iValue...

변경은 m_iValue가상 액세서 메소드를 사용하지 않고 직접 수행되므로 청취를 C구현하는 코드가에 C있고 B알 수 없으므로 "듣기"를 통한 관찰자 는 호출 되지 않습니다.

결론

계층 구조에 다이아몬드가 있으면 해당 계층 구조에 문제가있을 가능성이 95 %라는 의미입니다.


귀하의 '잘못 될 수있는 것'은 다중 상속이 아닌 기본 멤버에 직접 액세스하기 때문입니다. 제거 'B'와 동일한 문제가 기본 규칙을 :. '는 개인되지 않은 경우, 그것은 가상해야한다'문제를 피할 수 m_iValue 가상되지 않습니다 및 그 개인이어야한다.
크리스 도드

4
@Chris Dodd : 정확하지 않습니다. m_iValue로 발생하는 일은 모든 심볼 ( 예 : typedef, 멤버 변수, 멤버 함수, 기본 클래스로 캐스트 등 )에서 발생했을 것 입니다. 이것은 실제로 다중 상속 문제입니다. 사용자가 Java 방식으로 이동하는 대신 다중 상속을 올바르게 사용해야한다는 것을 인식하고 "다중 상속은 100 % 악의적입니다. 인터페이스로 해보자"는 결론을 내립니다.
paercebal

안녕하세요, 가상 키워드를 사용할 때 A의 사본이 하나만있을 것입니다. 제 질문은 B 또는 C에서 오는 것인지 어떻게 알 수 있습니까? 내 질문이 전혀 유효합니까?
user875036

@ user875036 : A는 B와 C에서 나옵니다. 실제로 가상은 몇 가지 사항을 변경합니다 (예 : D는 B 나 C가 아닌 A의 생성자를 호출합니다). B와 C (및 D) 모두 A에 대한 포인터
를가집니다

3
FWIW, 누군가 궁금해하는 경우, 멤버 변수 가상 일 수 없습니다 . virtual은 함수 의 지정자입니다 . SO 참조 : stackoverflow.com/questions/3698831/…
rholmes

34

가상 기반으로 다중 상속을 설명하려면 C ++ 객체 모델에 대한 지식이 필요합니다. 주제를 명확하게 설명하는 것은 주석 상자가 아닌 기사에서 가장 잘 수행됩니다.

이 주제에 대한 나의 모든 의구심을 해결 한 가장 읽기 쉬운 설명은 다음 기사입니다. http://www.phpcompiler.org/articles/virtualinheritance.html

그것을 읽은 후에는 주제에 대한 다른 것을 읽을 필요가 없습니다 (컴파일러 라이터가 아닌 한).


10

가상 기본 클래스는 인스턴스화 할 수없는 클래스입니다. 직접 기본 개체를 만들 수는 없습니다.

나는 당신이 매우 다른 두 가지를 혼란스럽게 생각합니다. 가상 상속은 추상 클래스와 다릅니다. 가상 상속은 함수 호출의 동작을 수정합니다. 때로는 모호한 함수 호출을 해결하고 때로는 비가 상 상속에서 기대하는 클래스 이외의 클래스에 대한 함수 호출 처리를 지연시킵니다.


7

OJ의 친절 설명에 추가하고 싶습니다.

가상 상속은 가격이 없으면 오지 않습니다. 가상의 모든 것과 마찬가지로 성능이 저하됩니다. 이 성능 적중에는 덜 우아 할 수있는 방법이 있습니다.

가상으로 파생하여 다이아몬드를 깨는 대신 다이아몬드에 다른 레이어를 추가하여 다음과 같은 것을 얻을 수 있습니다.

   B
  / \
D11 D12
 |   |
D21 D22
 \   /
  DD

어떤 클래스도 가상으로 상속되지 않으며 모두 공개적으로 상속됩니다. 클래스 D21과 D22는 아마도 private 함수를 선언함으로써 DD에 대해 모호한 가상 함수 f ()를 숨길 것입니다. 그들은 각각 랩퍼 함수 f1 () 및 f2 ()를 각각 정의하고, 각각 클래스-로컬 (비공개) f ()를 호출하여 충돌을 해결합니다. 클래스 DD는 D11 :: f ()를 원하면 f1 ()을 호출하고 D12 :: f ()를 원하면 f2 ()를 호출합니다. 래퍼를 인라인으로 정의하면 오버 헤드가 거의 없습니다.

물론 D11과 D12를 변경할 수 있다면 이러한 클래스 내에서 동일한 트릭을 수행 할 수 있지만 종종 그렇지 않습니다.


2
이것은 다소 우아하거나 모호성을 해결하는 문제가 아닙니다 (언제나 명시적인 xxx :: 사양을 사용할 수 있습니다). 비가 상 상속을 사용하면 클래스 DD의 모든 인스턴스에는 두 개의 독립적 인 B 인스턴스가 있습니다. 클래스에 하나의 비 정적 데이터 멤버가있는 즉시 가상 및 비가 상 상속은 구문 이상의 차이가 있습니다.
user3489112

@ user3489112 빨리 ... 아무것도 없습니다. 가상 상속과 비가 상 상속은 의미 상 기간이 다릅니다.
curiousguy


1

약간 혼란 스럽습니다. 나는 당신이 몇 가지 개념을 섞고 있는지 모른다.

OP에 가상 기본 클래스가 없습니다. 당신은 기본 수업이 있습니다.

당신은 가상 상속을했습니다. 이것은 일반적으로 다중 상속에서 사용되므로 여러 파생 클래스가 기본 클래스의 멤버를 재생하지 않고 사용합니다.

순수한 가상 함수를 가진 기본 클래스는 인스턴스화되지 않습니다. 이를 위해서는 Paul이 얻는 구문이 필요합니다. 파생 클래스가 해당 함수를 정의해야하므로 일반적으로 사용됩니다.

나는 당신이 요구하는 것을 완전히 얻지 못하기 때문에 이것에 대해 더 이상 설명하고 싶지 않습니다.


1
가상 상속에 사용되는 "기본 클래스"는 (정확한 상속 컨텍스트에서) "가상 기본 클래스"가됩니다.
Luc Hermitte

1

가상 함수에 대한 호출이 "오른쪽"클래스로 전달됨을 의미합니다.

C ++ FAQ Lite FTW.

간단히 말해서, 종종 "다이아몬드"계층 구조가 형성되는 다중 상속 시나리오에서 사용됩니다. 그런 다음 가상 상속은 해당 클래스에서 함수를 호출하고 함수가 해당 하위 클래스 위의 클래스 D1 또는 D2로 해석되어야 할 때 하위 클래스에서 작성된 모호성을 깰 것입니다. FAQ 항목을 참조하십시오다이어그램과 세부 사항 을 하십시오.

또한 강력한 기능인 자매 위임 에서도 사용됩니다 (심한 심연은 아님). 참조 FAQ를 .

또한 효과적인 C ++ 3 판 (40 판, 2 판)의 항목 40을 참조하십시오.


1

다이아몬드 상속 실행 가능 사용 예

이 예제는 일반적인 시나리오에서 가상 기본 클래스를 사용하여 다이아몬드 상속을 해결하는 방법을 보여줍니다.

#include <cassert>

class A {
    public:
        A(){}
        A(int i) : i(i) {}
        int i;
        virtual int f() = 0;
        virtual int g() = 0;
        virtual int h() = 0;
};

class B : public virtual A {
    public:
        B(int j) : j(j) {}
        int j;
        virtual int f() { return this->i + this->j; }
};

class C : public virtual A {
    public:
        C(int k) : k(k) {}
        int k;
        virtual int g() { return this->i + this->k; }
};

class D : public B, public C {
    public:
        D(int i, int j, int k) : A(i), B(j), C(k) {}
        virtual int h() { return this->i + this->j + this->k; }
};

int main() {
    D d = D(1, 2, 4);
    assert(d.f() == 3);
    assert(d.g() == 5);
    assert(d.h() == 7);
}

2
assert(A::aDefault == 0);주 함수에서 컴파일 오류가 발생합니다 : aDefault is not a member of Agcc 5.4.0 사용. 무엇을해야합니까?
SebNag

@SebTu 아 감사합니다. 복사 붙여 넣기에서 제거하는 것을 잊어 버렸습니다. 지금 제거하십시오. 그 예는 여전히 의미가 없어야합니다.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

0

가상 클래스는 아닙니다 가상 상속과 동일. 인스턴스화 할 수없는 가상 클래스, 가상 상속은 완전히 다른 것입니다.

Wikipedia는 내가 할 수있는 것보다 더 잘 설명합니다. http://en.wikipedia.org/wiki/Virtual_inheritance


6
C ++에는 "가상 클래스"와 같은 것은 없습니다. 그러나 주어진 상속과 관련하여 "가상"인 "가상 기본 클래스"가 있습니다. 당신이 참조하는 것은 공식적으로 "추상 클래스"입니다.
Luc Hermitte

@LucHermitte, C ++에는 확실히 가상 클래스가 있습니다. 이 옵션을 선택합니다 : en.wikipedia.org/wiki/Virtual_class를 .
Rafid

"오류 : '가상'은 기능에 대해서만 지정할 수 있습니다." 나는 이것이 어떤 언어인지 모른다. 그러나 C ++의 가상 클래스 와 같은 것은 없습니다 .
Luc Hermitte

0

정기 상속

일반적인 3 단계 비 다이아몬드 비가 상 상속 상속을 사용하면 가장 파생 된 새 객체를 인스턴스화 할 때 new가 호출되고 컴파일러에 의해 클래스 유형에서 객체에 필요한 크기가 확인되어 새로운 객체로 전달됩니다.

새로운 서명이 있습니다 :

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)

그리고 전화 malloc 하여 void 포인터를 반환합니다.

그런 다음 가장 파생 된 객체의 생성자로 전달되어 즉시 중간 생성자를 호출 한 다음 중간 생성자가 즉시 기본 생성자를 호출합니다. 그런 다음베이스는 객체 시작시 가상 테이블에 대한 포인터를 저장 한 후 그 속성을 저장합니다. 그런 다음 중간 생성자로 돌아가서 가상 테이블 포인터를 동일한 위치에 저장 한 다음 기본 생성자가 저장 한 속성 뒤에 해당 속성을 저장합니다. 가장 파생 된 생성자로 돌아가서 동일한 위치에 가상 테이블에 대한 포인터를 저장 한 다음 중간 생성자가 저장 한 속성 뒤에 해당 속성을 저장합니다.

가상 테이블 포인터를 덮어 쓰므로 가상 테이블 포인터는 항상 가장 파생 된 클래스 중 하나가됩니다. 가상은 가장 파생 된 클래스로 전파되므로 함수가 중간 클래스에서 가상 인 경우 가장 파생 된 클래스에서는 가상이지만 기본 클래스는 아닙니다. 가장 파생 된 클래스의 인스턴스를 기본 클래스에 대한 포인터에 다형성으로 캐스팅하면 컴파일러는이를 가상 테이블에 대한 간접 호출로 해결하지 않고 대신 함수를 직접 호출합니다 A::function(). 함수가 캐스트 한 유형에 대해 가상 인 경우 항상 가장 파생 된 클래스의 가상 테이블에 대한 호출로 해석됩니다. 해당 유형에 대해 가상이 아닌 경우 호출합니다.Type::function() 객체 포인터를 하여 전달하고 유형으로 캐스트합니다.

실제로 가상 테이블에 대한 포인터를 말할 때 실제로 가상 테이블에 대한 오프셋은 항상 16입니다.

vtable for Base:
        .quad   0
        .quad   typeinfo for Base
        .quad   Base::CommonFunction()
        .quad   Base::VirtualFunction()

pointer is typically to the first function i.e. 

        mov     edx, OFFSET FLAT:vtable for Base+16

virtual파생 된 클래스가 가상이기 때문에 파생 된 클래스에서는 다시 전파 될 필요가 없습니다. 그러나 함수가 유형 정의를 상속하는 클래스를 확인할 필요없이 함수가 실제로 가상 함수임을 표시하는 데 사용할 수 있습니다.

override 이 함수는 무언가를 재정의하고 그렇지 않으면 컴파일러 오류를 발생시키는 다른 컴파일러 가드입니다.

= 0 이것은 추상 함수임을 의미합니다.

final 더 파생 된 클래스에서 가상 함수가 다시 구현되지 않도록하고 가장 파생 된 클래스의 가상 테이블에 해당 클래스의 최종 함수가 포함되도록합니다.

= default 컴파일러가 기본 구현을 사용한다는 것을 문서에서 명시 적으로 만듭니다.

= delete 이것에 대한 호출이 시도되면 컴파일러 오류를 제공

가상 상속

치다

class Base
  {
      int a = 1;
      int b = 2;
  public:
      void virtual CommonFunction(){} ;
      void virtual VirtualFunction(){} ;
  };


class DerivedClass1: virtual public Base
  {
      int c = 3;
  public:
    void virtual DerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
  };

  class DerivedClass2 : virtual public Base
 {
     int d = 4;
 public:
     //void virtual DerivedCommonFunction(){} ;    
     void virtual VirtualFunction(){} ;
     void virtual DerivedCommonFunction2(){} ;
 };

class DerivedDerivedClass :  public DerivedClass1, public DerivedClass2
 {
   int e = 5;
 public:
     void virtual DerivedDerivedCommonFunction(){} ;
     void virtual VirtualFunction(){} ;
 };

 int main () {
   DerivedDerivedClass* d = new DerivedDerivedClass;
   d->VirtualFunction();
   d->DerivedCommonFunction();
   d->DerivedCommonFunction2();
   d->DerivedDerivedCommonFunction();
   ((DerivedClass2*)d)->DerivedCommonFunction2();
   ((Base*)d)->VirtualFunction();
 }

베이스 클래스를 실제로 상속하지 않으면 다음과 같은 객체를 얻게됩니다.

이 대신에 :

즉, 2 개의 기본 개체가 있습니다.

새가 호출 된 후 위의 가상 다이아몬드 상속 상황에서, 그것은 가장 파생 된 생성자를 호출하고 생성자에서, 그것은 대신 호출 호출하는 가상 테이블 테이블에 오프셋을 통과하는 모든 3 파생 생성자 호출 DerivedClass1::DerivedClass1()DerivedClass2::DerivedClass2() 다음 해당 모두 호출 및Base::Base()

다음은 디버그 모드 -O0에서 모두 컴파일되므로 중복 어셈블리가 있습니다.

main:
.LFB8:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 24
        mov     edi, 48 //pass size to new
        call    operator new(unsigned long) //call new
        mov     rbx, rax  //move the address of the allocation to rbx
        mov     rdi, rbx  //move it to rdi i.e. pass to the call
        call    DerivedDerivedClass::DerivedDerivedClass() [complete object constructor] //construct on this address
        mov     QWORD PTR [rbp-24], rbx  //store the address of the object on the stack as d
DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]:
.LFB20:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     QWORD PTR [rbp-8], rdi
.LBB5:
        mov     rax, QWORD PTR [rbp-8] // object address now in rax 
        add     rax, 32 //increment address by 32
        mov     rdi, rax // move object address+32 to rdi i.e. pass to call
        call    Base::Base() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+8 //move address of VTT+8 to edx
        mov     rsi, rdx //pass VTT+8 address as 2nd parameter 
        mov     rdi, rax //object address as first
        call    DerivedClass1::DerivedClass1() [base object constructor]
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        add     rax, 16  //increment object address by 16
        mov     edx, OFFSET FLAT:VTT for DerivedDerivedClass+24  //store address of VTT+24 in edx
        mov     rsi, rdx //pass address of VTT+24 as second parameter
        mov     rdi, rax //address of object as first
        call    DerivedClass2::DerivedClass2() [base object constructor]
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+24 //move this to edx
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        mov     QWORD PTR [rax], rdx. //store address of vtable for DerivedDerivedClass+24 at the start of the object
        mov     rax, QWORD PTR [rbp-8] // object address now in rax
        add     rax, 32  // increment object address by 32
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+120 //move this to edx
        mov     QWORD PTR [rax], rdx  //store vtable for DerivedDerivedClass+120 at object+32 (Base) 
        mov     edx, OFFSET FLAT:vtable for DerivedDerivedClass+72 //store this in edx
        mov     rax, QWORD PTR [rbp-8] //move object address to rax
        mov     QWORD PTR [rax+16], rdx //store vtable for DerivedDerivedClass+72 at object+16 (DerivedClass2)
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax+28], 5
.LBE5:
        nop
        leave
        ret

Base::Base()객체 오프셋 32에 대한 포인터로 호출 합니다. Base는 수신 한 주소와 그 뒤에있는 구성원에서 가상 테이블에 대한 포인터를 저장합니다.

Base::Base() [base object constructor]:
.LFB11:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //stores address of object on stack (-O0)
.LBB2:
        mov     edx, OFFSET FLAT:vtable for Base+16  //puts vtable for Base+16 in edx
        mov     rax, QWORD PTR [rbp-8] //copies address of object from stack to rax
        mov     QWORD PTR [rax], rdx  //stores it address of object
        mov     rax, QWORD PTR [rbp-8] //copies address of object on stack to rax again
        mov     DWORD PTR [rax+8], 1 //stores a = 1 in the object
        mov     rax, QWORD PTR [rbp-8] //junk from -O0
        mov     DWORD PTR [rax+12], 2  //stores b = 2 in the object
.LBE2:
        nop
        pop     rbp
        ret

DerivedDerivedClass::DerivedDerivedClass()그런 다음 DerivedClass1::DerivedClass1()객체 오프셋 0에 대한 포인터로 호출 하고 주소를 전달합니다.VTT for DerivedDerivedClass+8

DerivedClass1::DerivedClass1() [base object constructor]:
.LFB14:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi //address of object
        mov     QWORD PTR [rbp-16], rsi  //address of VTT+8
.LBB3:
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rdx, QWORD PTR [rax]     //address of DerivedClass1-in-DerivedDerivedClass+24 now in rdx
        mov     rax, QWORD PTR [rbp-8]   //address of object now in rax
        mov     QWORD PTR [rax], rdx     //store address of DerivedClass1-in-.. in the object
        mov     rax, QWORD PTR [rbp-8]  // address of object now in rax
        mov     rax, QWORD PTR [rax]    //address of DerivedClass1-in.. now implicitly in rax
        sub     rax, 24                 //address of DerivedClass1-in-DerivedDerivedClass+0 now in rax
        mov     rax, QWORD PTR [rax]    //value of 32 now in rax
        mov     rdx, rax                // now in rdx
        mov     rax, QWORD PTR [rbp-8]  //address of object now in rax
        add     rdx, rax                //address of object+32 now in rdx
        mov     rax, QWORD PTR [rbp-16]  //address of VTT+8 now in rax
        mov     rax, QWORD PTR [rax+8]   //address of DerivedClass1-in-DerivedDerivedClass+72 (Base::CommonFunction()) now in rax
        mov     QWORD PTR [rdx], rax     //store at address object+32 (offset to Base)
        mov     rax, QWORD PTR [rbp-8]  //store address of object in rax, return
        mov     DWORD PTR [rax+8], 3    //store its attribute c = 3 in the object
.LBE3:
        nop
        pop     rbp
        ret
VTT for DerivedDerivedClass:
        .quad   vtable for DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass1-in-DerivedDerivedClass+72
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+24
        .quad   construction vtable for DerivedClass2-in-DerivedDerivedClass+72
        .quad   vtable for DerivedDerivedClass+120
        .quad   vtable for DerivedDerivedClass+72

construction vtable for DerivedClass1-in-DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedClass1
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedClass1::VirtualFunction()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedClass1
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass1::VirtualFunction()
construction vtable for DerivedClass2-in-DerivedDerivedClass:
        .quad   16
        .quad   0
        .quad   typeinfo for DerivedClass2
        .quad   DerivedClass2::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -16
        .quad   0
        .quad   -16
        .quad   typeinfo for DerivedClass2
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedClass2::VirtualFunction()
vtable for DerivedDerivedClass:
        .quad   32
        .quad   0
        .quad   typeinfo for DerivedDerivedClass
        .quad   DerivedClass1::DerivedCommonFunction()
        .quad   DerivedDerivedClass::VirtualFunction()
        .quad   DerivedDerivedClass::DerivedDerivedCommonFunction()
        .quad   16
        .quad   -16
        .quad   typeinfo for DerivedDerivedClass
        .quad   non-virtual thunk to DerivedDerivedClass::VirtualFunction()
        .quad   DerivedClass2::DerivedCommonFunction2()
        .quad   -32
        .quad   0
        .quad   -32
        .quad   typeinfo for DerivedDerivedClass
        .quad   Base::CommonFunction()
        .quad   virtual thunk to DerivedDerivedClass::VirtualFunction()

virtual thunk to DerivedClass1::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK0
virtual thunk to DerivedClass2::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK1
virtual thunk to DerivedDerivedClass::VirtualFunction():
        mov     r10, QWORD PTR [rdi]
        add     rdi, QWORD PTR [r10-32]
        jmp     .LTHUNK2
non-virtual thunk to DerivedDerivedClass::VirtualFunction():
        sub     rdi, 16
        jmp     .LTHUNK3

        .set    .LTHUNK0,DerivedClass1::VirtualFunction()
        .set    .LTHUNK1,DerivedClass2::VirtualFunction()
        .set    .LTHUNK2,DerivedDerivedClass::VirtualFunction()
        .set    .LTHUNK3,DerivedDerivedClass::VirtualFunction()

DerivedDerivedClass::DerivedDerivedClass()다음 개체 + (16)의 주소를위한 VTT의 어드레스 전달 DerivedDerivedClass+24DerivedClass2::DerivedClass2()조립 동일 그 DerivedClass1::DerivedClass1()라인을 제외한 mov DWORD PTR [rax+8], 3분명히하는 대신 4 3 갖는다 d = 4.

그런 다음 객체의 3 개의 가상 테이블 포인터 DerivedDerivedClass를 해당 클래스의 표현에 대한 vtable의 오프셋에 대한 포인터로 바꿉니다 .

d->VirtualFunction();:

        mov     rax, QWORD PTR [rbp-24] //store pointer to virtual table in rax 
        mov     rax, QWORD PTR [rax] //dereference and store in rax
        add     rax, 8 // call the 2nd function in the table
        mov     rdx, QWORD PTR [rax] //dereference 
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

d->DerivedCommonFunction2();:

        mov     rax, QWORD PTR [rbp-24]
        lea     rdx, [rax+16]
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax+16]
        add     rax, 8
        mov     rax, QWORD PTR [rax]
        mov     rdi, rdx
        call    rax

d->DerivedDerivedCommonFunction();:

        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        add     rax, 16
        mov     rdx, QWORD PTR [rax]
        mov     rax, QWORD PTR [rbp-24]
        mov     rdi, rax
        call    rdx

((DerivedClass2*)d)->DerivedCommonFunction2();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L14
        mov     rax, QWORD PTR [rbp-24]
        add     rax, 16
        jmp     .L15
.L14:
        mov     eax, 0
.L15:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L18
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, 16
        jmp     .L19
.L18:
        mov     edx, 0
.L19:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx

((Base*)d)->VirtualFunction();:

        cmp     QWORD PTR [rbp-24], 0
        je      .L20
        mov     rax, QWORD PTR [rbp-24]
        mov     rax, QWORD PTR [rax]
        sub     rax, 24
        mov     rax, QWORD PTR [rax]
        mov     rdx, rax
        mov     rax, QWORD PTR [rbp-24]
        add     rax, rdx
        jmp     .L21
.L20:
        mov     eax, 0
.L21:
        cmp     QWORD PTR [rbp-24], 0
        cmp     QWORD PTR [rbp-24], 0
        je      .L24
        mov     rdx, QWORD PTR [rbp-24]
        mov     rdx, QWORD PTR [rdx]
        sub     rdx, 24
        mov     rdx, QWORD PTR [rdx]
        mov     rcx, rdx
        mov     rdx, QWORD PTR [rbp-24]
        add     rdx, rcx
        jmp     .L25
.L24:
        mov     edx, 0
.L25:
        mov     rdx, QWORD PTR [rdx]
        add     rdx, 8
        mov     rdx, QWORD PTR [rdx]
        mov     rdi, rax
        call    rdx
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.