Мы собираемся использовать тот же код, что и в предыдущем разделе, и теперь продолжим код с того места, где оставили его в прошлый раз.

// VirtualFunctionCalls.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>

using namespace std;

class Parent {

protected:
    int valueSet;

public:
    Parent() { valueSet = 0; }
    ~Parent() {}

    virtual void CallMe();
    virtual int SetMe(int val);
};

void Parent::CallMe() { cout << "Called Parent" << endl; }
int Parent::SetMe(int val) { valueSet = val; return valueSet; }

class Child : public Parent
{
public:
    Child() {}
    ~Child() {}
    void CallMe() override;
};

void Child::CallMe() { cout << "Called Child" << endl; }

int main()
{
    Child* ch = new Child();

    ch->CallMe();

    ch->Parent::CallMe();
}
// SimpleInheritanceCalls.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <iostream>

using namespace std;

class Parent {

protected:
    int valueSet;

public:
    Parent() { valueSet = 0; }
    ~Parent() {}

    void CallMe();
    int SetMe(int val);
};

void Parent::CallMe() { cout << "Called Parent" << endl; }
int Parent::SetMe(int val) { valueSet = val; return valueSet; }

class Child : public Parent
{
public:
    Child() {}
    ~Child() {}
    void CallMe();
};

void Child::CallMe() 
{
    cout << "Called Child" << endl;
}


int main()
{
    Child* ch = new Child();

    ch->CallMe();

    Parent* prt = dynamic_cast<Parent *>(ch);

    prt->CallMe();
}
0:000> uf main
VirtualFunctionCalls!main:
00007ff7`edf22550 4055            push    rbp
00007ff7`edf22552 57              push    rdi
00007ff7`edf22553 4881ec48010000  sub     rsp,148h
00007ff7`edf2255a 488d6c2420      lea     rbp,[rsp+20h]
00007ff7`edf2255f b910000000      mov     ecx,10h
00007ff7`edf22564 e8e2eaffff      call    VirtualFunctionCalls!ILT+70(??2YAPEAX_KZ) (00007ff7`edf2104b)
00007ff7`edf22569 48898508010000  mov     qword ptr [rbp+108h],rax
00007ff7`edf22570 4883bd0801000000 cmp     qword ptr [rbp+108h],0
00007ff7`edf22578 7415            je      VirtualFunctionCalls!main+0x3f (00007ff7`edf2258f)  Branch

VirtualFunctionCalls!main+0x2a:
00007ff7`edf2257a 488b8d08010000  mov     rcx,qword ptr [rbp+108h]
00007ff7`edf22581 e8f9edffff      call    VirtualFunctionCalls!ILT+890(??0ChildQEAAXZ) (00007ff7`edf2137f)
00007ff7`edf22586 48898518010000  mov     qword ptr [rbp+118h],rax
00007ff7`edf2258d eb0b            jmp     VirtualFunctionCalls!main+0x4a (00007ff7`edf2259a)  Branch

VirtualFunctionCalls!main+0x3f:
00007ff7`edf2258f 48c7851801000000000000 mov qword ptr [rbp+118h],0

VirtualFunctionCalls!main+0x4a:
00007ff7`edf2259a 488b8518010000  mov     rax,qword ptr [rbp+118h]
00007ff7`edf225a1 488985e8000000  mov     qword ptr [rbp+0E8h],rax
00007ff7`edf225a8 488b85e8000000  mov     rax,qword ptr [rbp+0E8h]
00007ff7`edf225af 48894508        mov     qword ptr [rbp+8],rax
00007ff7`edf225b3 488b4508        mov     rax,qword ptr [rbp+8]
00007ff7`edf225b7 488b00          mov     rax,qword ptr [rax]
00007ff7`edf225ba 488b4d08        mov     rcx,qword ptr [rbp+8]
00007ff7`edf225be ff10            call    qword ptr [rax]
00007ff7`edf225c0 488b4d08        mov     rcx,qword ptr [rbp+8]
00007ff7`edf225c4 e837efffff      call    VirtualFunctionCalls!ILT+1275(?CallMeParentUEAAXXZ) (00007ff7`edf21500)
00007ff7`edf225c9 33c0            xor     eax,eax
00007ff7`edf225cb 488da528010000  lea     rsp,[rbp+128h]
00007ff7`edf225d2 5f              pop     rdi
00007ff7`edf225d3 5d              pop     rbp
00007ff7`edf225d4 c3              ret
0:000> uf main
SimpleInheritanceCalls!main:
00007ff7`e4f52500 4055            push    rbp
00007ff7`e4f52502 57              push    rdi
00007ff7`e4f52503 4881ec68010000  sub     rsp,168h
00007ff7`e4f5250a 488d6c2420      lea     rbp,[rsp+20h]
00007ff7`e4f5250f b904000000      mov     ecx,4
00007ff7`e4f52514 e832ebffff      call    SimpleInheritanceCalls!ILT+70(??2YAPEAX_KZ) (00007ff7`e4f5104b)
00007ff7`e4f52519 48898528010000  mov     qword ptr [rbp+128h],rax
00007ff7`e4f52520 4883bd2801000000 cmp     qword ptr [rbp+128h],0
00007ff7`e4f52528 7415            je      SimpleInheritanceCalls!main+0x3f (00007ff7`e4f5253f)  Branch

SimpleInheritanceCalls!main+0x2a:
00007ff7`e4f5252a 488b8d28010000  mov     rcx,qword ptr [rbp+128h]
00007ff7`e4f52531 e849eeffff      call    SimpleInheritanceCalls!ILT+890(??0ChildQEAAXZ) (00007ff7`e4f5137f)
00007ff7`e4f52536 48898538010000  mov     qword ptr [rbp+138h],rax
00007ff7`e4f5253d eb0b            jmp     SimpleInheritanceCalls!main+0x4a (00007ff7`e4f5254a)  Branch

SimpleInheritanceCalls!main+0x3f:
00007ff7`e4f5253f 48c7853801000000000000 mov qword ptr [rbp+138h],0

SimpleInheritanceCalls!main+0x4a:
00007ff7`e4f5254a 488b8538010000  mov     rax,qword ptr [rbp+138h]
00007ff7`e4f52551 48898508010000  mov     qword ptr [rbp+108h],rax
00007ff7`e4f52558 488b8508010000  mov     rax,qword ptr [rbp+108h]
00007ff7`e4f5255f 48894508        mov     qword ptr [rbp+8],rax
00007ff7`e4f52563 488b4d08        mov     rcx,qword ptr [rbp+8]
00007ff7`e4f52567 e899efffff      call    SimpleInheritanceCalls!ILT+1280(?CallMeChildQEAAXXZ) (00007ff7`e4f51505)
00007ff7`e4f5256c 488b4508        mov     rax,qword ptr [rbp+8]
00007ff7`e4f52570 48894528        mov     qword ptr [rbp+28h],rax
00007ff7`e4f52574 488b4d28        mov     rcx,qword ptr [rbp+28h]
00007ff7`e4f52578 e879efffff      call    SimpleInheritanceCalls!ILT+1265(?CallMeParentQEAAXXZ) (00007ff7`e4f514f6)
00007ff7`e4f5257d 33c0            xor     eax,eax
00007ff7`e4f5257f 488da548010000  lea     rsp,[rbp+148h]
00007ff7`e4f52586 5f              pop     rdi
00007ff7`e4f52587 5d              pop     rbp
00007ff7`e4f52588 c3              ret

Итак, продолжим

Обратите внимание на код: перед каждым вызовом функции-члена C++ указатель на this неявно передается в качестве первого аргумента через регистр RCX (если вы не уверены, что это означает, взгляните на Соглашения о вызовах X64 для Windows AKA: Windows ABI и это на C++).

В случае класса с виртуальными методами мы можем видеть, как вызовы происходят после загрузки указателя и разыменования его для выполнения вызова, когда происходит вызов, значение, указанное регистром RAX, равно заголовку VTable для Ребенок, так как объявление «CallMe» предшествует «SetMe» в исходном коде, это также первая функция в VTable (можно ожидать, что это также следует за C/C++). правила компоновки структур).

0:000> r
rax=00007ff7edf2bea8 rbx=0000000000000000 rcx=000001e0b135c710
rdx=cdcdcdcdcdcdcdcd rsi=0000000000000000 rdi=0000000000000000
rip=00007ff7edf225be rsp=00000032e79df880 rbp=00000032e79df8a0
 r8=0000000000000010  r9=00007ffd91ca0508 r10=0000000000000000
r11=000001e0b135c710 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
VirtualFunctionCalls!main+0x6e:
00007ff7`edf225be ff10            call    qword ptr [rax] ds:00007ff7`edf2bea8={VirtualFunctionCalls!ILT+1270(?CallMeChildUEAAXXZ) (00007ff7`edf214fb)}
0:000> dqs 00007ff7edf2bea8 l2
00007ff7`edf2bea8  00007ff7`edf214fb VirtualFunctionCalls!ILT+1270(?CallMeChildUEAAXXZ)
00007ff7`edf2beb0  00007ff7`edf214f6 VirtualFunctionCalls!ILT+1265(?SetMeParentUEAAHHZ)

Мы проходим через ILT (инкрементная таблица ссылок, только отладка) и заканчиваем функцией «CallMe», которая практически идентична для обоих.

Обратите внимание, что вызов функции «Parent CallMe» происходит одинаково для обоих?

Похоже, что поскольку Parent ни от кого не наследует, адрес для функции «CallMe» может быть выведен статически во время компиляции, и никаких манипуляций с указателем/разыменования не требуется.

Эта часть была намного проще, чем конструкторы, верно?

Но вы видели дополнительные шаги, необходимые после вызова виртуальной функции, это хорошо известное снижение производительности, возникающее из-за полиморфного кода.

Это совпадение, что код для обоих основных методов имеет одинаковое количество инструкций, но помните, что мы создаем дополнительную переменную для кода, использующего простое наследование, поэтому эти дополнительные инструкции делают код простого наследования равным по количеству. инструкции (дополнительное объявление указателя и dynamic_cast‹›).

Мы можем вернуться к этому позже, чтобы увидеть, как это работает с более сложными классами, такими как те, которые являются частью более крупной цепочки наследования.