#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);
}
main.cpp:27:7: warning: virtual base ‘A’ inaccessible in ‘D’ due to ambiguity [-Wextra]
27 | class D : public B, public C {
| ^
main.cpp: In member function ‘virtual int D::h()’:
main.cpp:30:40: error: request for member ‘i’ is ambiguous
30 | virtual int h() { return this->i + this->j + this->k; }
| ^
main.cpp:7:13: note: candidates are: ‘int A::i’
7 | int i;
| ^
main.cpp:7:13: note: ‘int A::i’
main.cpp: In function ‘int main()’:
main.cpp:34:20: error: invalid cast to abstract class type ‘D’
34 | D d = D(1, 2, 4);
| ^
main.cpp:27:7: note: because the following virtual functions are pure within ‘D’:
27 | class D : public B, public C {
| ^
main.cpp:8:21: note: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:34:7: error: cannot declare variable ‘d’ to be of abstract type ‘D’
34 | D d = D(1, 2, 4);
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:35:14: error: request for member ‘f’ is ambiguous
35 | assert(d.f() == 3);
| ^
main.cpp:8:21: note: candidates are: ‘virtual int A::f()’
8 | virtual int f() = 0;
| ^
main.cpp:17:21: note: ‘virtual int B::f()’
17 | virtual int f() { return this->i + this->j; }
| ^
In file included from /usr/include/c++/9/cassert:44,
from main.cpp:1:
main.cpp:36:14: error: request for member ‘g’ is ambiguous
36 | assert(d.g() == 5);
| ^
main.cpp:9:21: note: candidates are: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
main.cpp:24:21: note: ‘virtual int C::g()’
24 | virtual int g() { return this->i + this->k; }
| ^
main.cpp:9:21: note: ‘virtual int A::g()’
9 | virtual int g() = 0;
| ^
./main.out
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
class Base
{
int a = 1;
int b = 2;
public:
void virtual CommonFunction(){} ; //define empty method body
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();
}
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 the d pointer variable on -O0, will be optimised off on -Ofast if the address of the pointer itself isn't taken in the code, because this address does not need to be on the stack, it can just be passed in a register to the subsequent methods
顺便说一句,如果代码是DerivedDerivedClass d = DerivedDerivedClass(), main函数将看起来像这样:
main:
push rbp
mov rbp, rsp
sub rsp, 48 // make room for and zero 48 bytes on the stack for the 48 byte object, no extra padding required as the frame is 64 bytes with `rbp` and return address of the function it calls (no stack params are passed to any function it calls), hence rsp will be aligned by 16 assuming it was aligned at the start of this frame
mov QWORD PTR [rbp-48], 0
mov QWORD PTR [rbp-40], 0
mov QWORD PTR [rbp-32], 0
mov QWORD PTR [rbp-24], 0
mov QWORD PTR [rbp-16], 0
mov QWORD PTR [rbp-8], 0
lea rax, [rbp-48] // load the address of the cleared 48 bytes
mov rdi, rax // pass the address as a pointer to the 48 bytes cleared as the first parameter to the constructor
call DerivedDerivedClass::DerivedDerivedClass() [complete object constructor]
//address is not stored on the stack because the object is used directly -- there is no pointer variable -- d refers to the object on the stack as opposed to being a pointer
回到最初的例子,DerivedDerivedClass构造函数:
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 (DerivedClass1 subobject)
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 DerivedClass2 subobject 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 // stores e = 5 in the object
.LBE5:
nop
leave
ret
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()然后用指向偏移量为0的对象的指针调用DerivedClass1::DerivedClass1(),并传递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] //derference VTT+8+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 //(DerivedClass1 uses this to write its vtable pointer)
.quad construction vtable for DerivedClass1-in-DerivedDerivedClass+72 //(DerivedClass1 uses this to overwrite the base vtable pointer)
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+24
.quad construction vtable for DerivedClass2-in-DerivedDerivedClass+72
.quad vtable for DerivedDerivedClass+120 // DerivedDerivedClass supposed to use this to overwrite Bases's vtable pointer
.quad vtable for DerivedDerivedClass+72 // DerivedDerivedClass supposed to use this to overwrite DerivedClass2's vtable pointer
//although DerivedDerivedClass uses vtable for DerivedDerivedClass+72 and DerivedDerivedClass+120 directly to overwrite them instead of going through the VTT
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()
mov rax, QWORD PTR [rbp-24] //store pointer to object (and hence vtable pointer) in rax
mov rax, QWORD PTR [rax] //dereference this pointer to vtable pointer and store virtual table pointer in rax
add rax, 8 // add 8 to the pointer to get the 2nd function pointer in the table
mov rdx, QWORD PTR [rax] //dereference this pointer to get the address of the method to call
mov rax, QWORD PTR [rbp-24] //restore pointer to object in rax (-O0 is inefficient, yes)
mov rdi, rax //pass object to the method
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 //pass object to method
call rdx //call the first function in the table
d->DerivedCommonFunction2();:
mov rax, QWORD PTR [rbp-24] //get the object pointer
lea rdx, [rax+16] //get the address of the 2nd subobject in the object
mov rax, QWORD PTR [rbp-24] //get the object pointer
mov rax, QWORD PTR [rax+16] // get the vtable pointer of the 2nd subobject
add rax, 8 //call the 2nd function in this table
mov rax, QWORD PTR [rax] //get the address of the 2nd function
mov rdi, rdx //call it and pass the 2nd subobject to it
call rax
d->DerivedDerivedCommonFunction();:
mov rax, QWORD PTR [rbp-24] //get the object pointer
mov rax, QWORD PTR [rax] //get the vtable pointer
add rax, 16 //get the 3rd function in the first virtual table (which is where virtual functions that that first appear in the most derived class go, because they belong to the full object which uses the virtual table pointer at the start of the object)
mov rdx, QWORD PTR [rax] //get the address of the object
mov rax, QWORD PTR [rbp-24]
mov rdi, rax //call it and pass the whole object to it
call rdx
((DerivedClass2*)d)->DerivedCommonFunction2();:
//it casts the object to its subobject and calls the corresponding method in its virtual table, which will be a non-virtual thunk
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();:
//it casts the object to its subobject and calls the corresponding function in its virtual table, which will be a virtual thunk
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