Мы собираемся использовать тот же код, что и в предыдущем разделе, и теперь продолжим код с того места, где оставили его в прошлый раз.
// 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‹›).
Мы можем вернуться к этому позже, чтобы увидеть, как это работает с более сложными классами, такими как те, которые являются частью более крупной цепочки наследования.