Regelmäßige Vererbung
![]()
Bei der typischen 3-stufigen nicht-diamantenen nicht-virtuellen Vererbung wird beim Instanziieren eines neuen most-derived-object, new
wird aufgerufen und die für das Objekt auf dem Heap erforderliche Größe wird vom Compiler aus dem Klassentyp aufgelöst und an new übergeben.
new
hat eine Signatur:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
Und macht einen Aufruf an malloc
und gibt den Zeiger auf die Leere zurück
Diese Adresse wird dann an den Konstruktor des am weitesten abgeleiteten Objekts übergeben, der sofort den mittleren Konstruktor aufruft, und der mittlere Konstruktor ruft dann sofort den Basiskonstruktor auf. Der Basiskonstruktor speichert dann einen Zeiger auf seine virtuelle Tabelle am Anfang des Objekts und dann seine Attribute danach. Dieser kehrt dann zum mittleren Konstruktor zurück, der seinen Zeiger auf die virtuelle Tabelle an derselben Stelle speichert und dann seine Attribute nach den Attributen, die vom Basiskonstruktor gespeichert worden wären. Dieser kehrt dann zum am weitesten abgeleiteten Konstruktor zurück, der einen Zeiger auf seine virtuelle Tabelle an derselben Stelle speichert und dann seine Attribute nach den Attributen speichert, die vom mittleren Konstruktor gespeichert worden wären.
Da der Zeiger auf die virtuelle Tabelle überschrieben wird, ist der Zeiger auf die virtuelle Tabelle am Ende immer der Zeiger der am meisten abgeleiteten Klasse. Wenn also eine Funktion in der mittleren Klasse virtuell ist, ist sie auch in der am weitesten abgeleiteten Klasse virtuell, aber nicht in der Basisklasse. Wenn Sie eine Instanz der am weitesten abgeleiteten Klasse polymorph in einen Zeiger auf die Basisklasse umwandeln, wird der Compiler dies nicht in einen indirekten Aufruf der virtuellen Tabelle auflösen und stattdessen die Funktion direkt aufrufen A::function()
. Wenn eine Funktion für den Typ, in den sie gecastet wurde, virtuell ist, führt sie zu einem Aufruf der virtuellen Tabelle, die immer diejenige der am meisten abgeleiteten Klasse ist. Wenn sie für diesen Typ nicht virtuell ist, dann wird sie einfach Type::function()
und übergeben Sie ihm den Objektzeiger, der in Type umgewandelt wird.
Wenn ich von einem Zeiger auf die virtuelle Tabelle spreche, handelt es sich eigentlich immer um einen Offset von 16 in der virtuellen Tabelle.
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
wird in weiter abgeleiteten Klassen nicht mehr benötigt, wenn es in einer weniger abgeleiteten Klasse virtuell ist, da es sich in Richtung der am meisten abgeleiteten Klasse nach unten fortpflanzt. Sie kann jedoch verwendet werden, um zu zeigen, dass die Funktion tatsächlich eine virtuelle Funktion ist, ohne dass die Typdefinitionen der Klassen, die sie erbt, überprüft werden müssen. Wenn eine Funktion als virtuell deklariert wird, wird ab diesem Zeitpunkt nur noch die letzte Implementierung in der Vererbungskette verwendet. Davor kann sie jedoch immer noch nicht-virtuell verwendet werden, wenn das Objekt in einen Typ einer Klasse gecastet wird, die in der Vererbungskette vor derjenigen liegt, die diese Methode definiert. Sie kann in mehreren Klassen in der Kette vor ihr nicht-virtuell definiert werden, bevor die Virtualität für eine Methode dieses Namens und dieser Signatur beginnt, und sie werden ihre eigenen Methoden verwenden, wenn sie referenziert werden (und alle Klassen nach dieser Definition in der Kette werden diese Definition verwenden, wenn sie keine eigene Definition haben, im Gegensatz zu virtual, das immer die endgültige Definition verwendet). Wenn eine Methode als virtuell deklariert wird, muss sie in dieser Klasse oder einer abgeleiteten Klasse in der Vererbungskette für das vollständige Objekt, das konstruiert wurde, implementiert werden, um verwendet werden zu können.
override
ist ein weiterer Compilerschutz, der besagt, dass diese Funktion etwas überschreibt, und wenn das nicht der Fall ist, einen Compilerfehler auslöst.
= 0
bedeutet, dass es sich um eine abstrakte Funktion handelt
final
verhindert, dass eine virtuelle Funktion in einer weiter abgeleiteten Klasse erneut implementiert wird, und stellt sicher, dass die virtuelle Tabelle der am weitesten abgeleiteten Klasse die endgültige Funktion dieser Klasse enthält.
= default
macht in der Dokumentation deutlich, dass der Compiler die Standardimplementierung verwendet
= delete
einen Compiler-Fehler auslösen, wenn ein Aufruf dieser Funktion versucht wird
Wenn Sie eine nicht-virtuelle Funktion aufrufen, wird sie zur richtigen Methodendefinition aufgelöst, ohne die virtuelle Tabelle zu durchlaufen. Wenn Sie eine virtuelle Funktion aufrufen, deren endgültige Definition in einer geerbten Klasse liegt, wird deren virtuelle Tabelle verwendet und das Unterobjekt automatisch an sie übergeben, wenn Sie den Objektzeiger beim Aufruf der Methode nicht auf diesen Typ casten. Wenn Sie eine virtuelle Funktion, die in der am weitesten abgeleiteten Klasse definiert ist, mit einem Zeiger dieses Typs aufrufen, verwendet sie ihre virtuelle Tabelle, die sich am Anfang des Objekts befindet. Wenn Sie sie auf einem Zeiger eines geerbten Typs aufrufen und die Funktion in dieser Klasse ebenfalls virtuell ist, dann wird sie den vtable-Zeiger dieses Unterobjekts verwenden, der im Fall des ersten Unterobjekts derselbe Zeiger sein wird wie der der am weitesten abgeleiteten Klasse, der keinen Thunk enthalten wird, da die Adresse des Objekts und des Unterobjekts dieselbe ist, und daher ist es genauso einfach, wie wenn die Methode diesen Zeiger automatisch umbesetzt, aber im Fall eines 2, wird seine vtable einen nicht-virtuellen Thunk enthalten, um den Zeiger des Objekts vom geerbten Typ in den Typ zu konvertieren, den die Implementierung in der am meisten abgeleiteten Klasse erwartet, nämlich das vollständige Objekt, und daher den Zeiger des Unterobjekts so verschieben, dass er auf das vollständige Objekt zeigt, und im Fall des Basis-Unterobjekts einen virtuellen Thunk erfordern, um den Zeiger auf die Basis auf das vollständige Objekt zu verschieben, so dass er vom Parametertyp des versteckten Objekts der Methode umbesetzt werden kann.
Die Verwendung des Objekts mit einem Referenzoperator und nicht durch einen Zeiger (Dereferenzoperator) bricht den Polymorphismus und behandelt virtuelle Methoden wie reguläre Methoden. Dies liegt daran, dass polymorphes Casting bei Nicht-Zeigertypen aufgrund von Slicing nicht möglich ist.
Virtuelle Vererbung
Erwägen Sie
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();
}
Wenn Sie die Klasse bass nicht virtuell erben, erhalten Sie ein Objekt, das wie folgt aussieht:
![]()
Stattdessen:
![]()
D.h. es wird 2 Basisobjekte geben.
In der obigen Situation der Vererbung eines virtuellen Diamanten, nachdem new
aufgerufen wird, übergibt er die Adresse des zugewiesenen Speicherplatzes für das Objekt an den am weitesten abgeleiteten Konstruktor DerivedDerivedClass::DerivedDerivedClass()
, die die Base::Base()
zuerst, die ihre vtable in das dedizierte Unterobjekt der Basis schreibt, dann DerivedDerivedClass::DerivedDerivedClass()
ruft auf. DerivedClass1::DerivedClass1()
das seinen Zeiger auf die virtuelle Tabelle in sein Unterobjekt schreibt und den Zeiger des Basisunterobjekts am Ende des Objekts durch Konsultation der übergebenen VTT überschreibt, und ruft dann DerivedClass1::DerivedClass1()
dasselbe zu tun, und schließlich DerivedDerivedClass::DerivedDerivedClass()
überschreibt alle 3 Zeiger mit seinem virtuellen Tabellenzeiger für diese geerbte Klasse. Dies ist anstelle von (wie im 1. Bild oben dargestellt) DerivedDerivedClass::DerivedDerivedClass()
aufrufen DerivedClass1::DerivedClass1()
und dass der Aufruf Base::Base()
(wodurch der virtuelle Zeiger überschrieben wird), Rückkehr, Verschieben der Adresse zum nächsten Unterobjekt, Aufruf von DerivedClass2::DerivedClass2()
und dann auch der Aufruf Base::Base()
, überschreibt diesen virtuellen Zeiger, kehrt zurück und DerivedDerivedClass
Konstruktor, der beide virtuellen Zeiger mit seinem virtuellen Tabellenzeiger überschreibt (in diesem Fall enthält die virtuelle Tabelle des am weitesten abgeleiteten Konstruktors 2 Untertabellen anstelle von 3).
Das Folgende wird im Debug-Modus -O0 kompiliert, so dass es redundante Assemblierung gibt
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
Nebenbei bemerkt: Wäre der Code DerivedDerivedClass d = DerivedDerivedClass()
die main
Funktion würde wie folgt aussehen:
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
Um auf das ursprüngliche Beispiel zurückzukommen, die DerivedDerivedClass
Konstrukteur:
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
El DerivedDerivedClass
Konstruktoraufrufe Base::Base()
mit einem Zeiger auf das Objekt offset 32. Base speichert einen Zeiger auf seine virtuelle Tabelle an der Adresse, die es erhält, und seine Mitglieder danach.
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()
ruft dann DerivedClass1::DerivedClass1()
mit einem Zeiger auf den Objekt-Offset 0 und übergibt außerdem die Adresse von 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()
Jede geerbte Klasse hat ihre eigene virtuelle Konstruktionstabelle und die am meisten abgeleitete Klasse, DerivedDerivedClass
hat eine virtuelle Tabelle mit einer Untertabelle für jedes Teilobjekt und verwendet den Zeiger auf die Untertabelle, um den Zeiger auf die Konstruktion vtable zu überschreiben, den der Konstruktor der geerbten Klasse für jedes Teilobjekt gespeichert hat. Jede virtuelle Methode, die einen Thunk benötigt (ein virtueller Thunk verschiebt den Objektzeiger von der Basis auf den Anfang des Objekts und ein nicht-virtueller Thunk verschiebt den Objektzeiger von einem Objekt der geerbten Klasse, das nicht das Basisobjekt ist, auf den Anfang des gesamten Objekts vom Typ DerivedDerivedClass
). El DerivedDerivedClass
Konstruktor verwendet auch eine virtuelle Tabellentabelle (VTT) als serielle Liste aller virtuellen Tabellenzeiger, die er verwenden muss, und übergibt sie an jeden Konstruktor (zusammen mit der Adresse des Unterobjekts, für das der Konstruktor bestimmt ist), die sie verwenden, um ihren und den vtable-Zeiger der Basis zu überschreiben.
DerivedDerivedClass::DerivedDerivedClass()
übergibt dann die Adresse des Objekts+16 und die Adresse von VTT für DerivedDerivedClass+24
a DerivedClass2::DerivedClass2()
dessen Aufbau identisch ist mit DerivedClass1::DerivedClass1()
mit Ausnahme der Zeile mov DWORD PTR [rax+8], 3
die offensichtlich eine 4 statt einer 3 hat für d = 4
.
Danach werden alle 3 virtuellen Tabellenzeiger im Objekt durch Zeiger auf Offsets in DerivedDerivedClass
vtable in die Repräsentation für diese Klasse.
Die Aufforderung an d->VirtualFunction()
en main
:
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