虚函数是面向对象程序设计的关键组成部分。对于具有虚函数的类而言,构造函数和析构函数的识别流程更加简单。而且,在类中定义了虚函数之后,如果没有提供默认的构造函数,编译器必须提供默认的构造函数。
对象的多态性需要通过虚表和虚表指针来完成,虚表指针被定义在对象首地址的前4字节处,因此虚函数必须作为成员函数使用。由于非成员函数没有this指针,因此无法获得虚表指针,进而无法获取虚表,也就无法访问虚函数。
class CVirtual { public: ~CVirtual() { printf("~CVirtual"); } CVirtual() { } CVirtual(int nNumber) { m_nNumber = nNumber; } virtual int GetNumber() { return m_nNumber; } virtual void SetNumber(int nNumber) { m_nNumber = nNumber; } private: int m_nNumber; };
类中只定义一个int类型的数据成员,类大小却为8。当类中含有虚函数时,对象空间中第一项存放虚表指针。
35: // 获取含有虚函数表的类大小 36: int nSize = sizeof(CVirtual); 0007739D C7 45 EC 08 00 00 00 mov dword ptr [nSize],8
虚表在构造函数中设置
13: CVirtual() 00077170 55 push ebp 00077171 8B EC mov ebp,esp 00077173 81 EC CC 00 00 00 sub esp,0CCh 00077179 53 push ebx 0007717A 56 push esi 0007717B 57 push edi 0007717C 51 push ecx 0007717D 8D BD 34 FF FF FF lea edi,[ebp-0CCh] 00077183 B9 33 00 00 00 mov ecx,33h 00077188 B8 CC CC CC CC mov eax,0CCCCCCCCh 0007718D F3 AB rep stos dword ptr es:[edi] 0007718F 59 pop ecx 00077190 89 4D F8 mov dword ptr [this],ecx 00077193 8B 45 F8 mov eax,dword ptr [this] //取this指针 00077196 C7 00 54 2E 11 00 mov dword ptr [eax],offset CVirtual::`vftable' (0112E54h)//设置对象第一项为虚表。 14: { 15: } 0007719C 8B 45 F8 mov eax,dword ptr [this] 0007719F 5F pop edi 000771A0 5E pop esi 000771A1 5B pop ebx 000771A2 8B E5 mov esp,ebp 000771A4 5D pop ebp 000771A5 C3 ret
析构时也要还原自身虚表
9: ~CVirtual() 10: { 000771C0 55 push ebp 000771C1 8B EC mov ebp,esp 000771C3 81 EC CC 00 00 00 sub esp,0CCh 000771C9 53 push ebx 000771CA 56 push esi 000771CB 57 push edi 000771CC 51 push ecx 000771CD 8D BD 34 FF FF FF lea edi,[ebp-0CCh] 000771D3 B9 33 00 00 00 mov ecx,33h 000771D8 B8 CC CC CC CC mov eax,0CCCCCCCCh 000771DD F3 AB rep stos dword ptr es:[edi] 000771DF 59 pop ecx 000771E0 89 4D F8 mov dword ptr [this],ecx 000771E3 8B 45 F8 mov eax,dword ptr [this] 000771E6 C7 00 54 2E 11 00 mov dword ptr [eax],offset CVirtual::`vftable' (0112E54h) 11: printf("~CVirtual"); 000771EC 68 60 2E 11 00 push offset string "~CVirtual" (0112E60h) 000771F1 E8 CF A1 FF FF call _printf (0713C5h) 000771F6 83 C4 04 add esp,4 12: } 000771F9 5F pop edi 000771FA 5E pop esi 000771FB 5B pop ebx 000771FC 81 C4 CC 00 00 00 add esp,0CCh 12: } 00077202 3B EC cmp ebp,esp 00077204 E8 92 BC FF FF call __RTC_CheckEsp (072E9Bh) 00077209 8B E5 mov esp,ebp 0007720B 5D pop ebp 0007720C C3 ret
使用对象直接调用自身虚函数,不会间接访问虚表调用。
40: MyVirtual.SetNumber(argc); 002673C1 8B 45 08 mov eax,dword ptr [argc] 002673C4 50 push eax 002673C5 8D 4D DC lea ecx,[MyVirtual] 002673C8 E8 23 A2 FF FF call CVirtual::SetNumber (02615F0h) 41: printf("%d\r\n", MyVirtual.GetNumber()); 002673CD 8D 4D DC lea ecx,[MyVirtual] 002673D0 E8 FF B3 FF FF call CVirtual::GetNumber (02627D4h) 002673D5 50 push eax 002673D6 68 6C 2E 30 00 push offset string "%d\r\n" (0302E6Ch) 002673DB E8 E5 9F FF FF call _printf (02613C5h) 002673E0 83 C4 08 add esp,8
当使用对象指针,引用时需要使用虚表。
43: p = &MyVirtual; 001773E3 8D 45 DC lea eax,[MyVirtual] 001773E6 89 45 D0 mov dword ptr [p],eax 44: p->SetNumber(66); 001773E9 8B F4 mov esi,esp 001773EB 6A 42 push 42h 001773ED 8B 45 D0 mov eax,dword ptr [p] //eax 存对象地址 001773F0 8B 10 mov edx,dword ptr [eax]//EDX 存虚表指针 001773F2 8B 4D D0 mov ecx,dword ptr [p] 001773F5 8B 42 04 mov eax,dword ptr [edx+4] //取虚表第二项,即SetNumber函数 44: p->SetNumber(66); 001773F8 FF D0 call eax 001773FA 3B F4 cmp esi,esp 001773FC E8 9A BA FF FF call __RTC_CheckEsp (0172E9Bh) 45: printf("%d\r\n", p->GetNumber()); 00177401 8B 45 D0 mov eax,dword ptr [p] 00177404 8B 10 mov edx,dword ptr [eax] //edx存虚表指针 00177406 8B F4 mov esi,esp 00177408 8B 4D D0 mov ecx,dword ptr [p] 0017740B 8B 02 mov eax,dword ptr [edx] //虚表第一项 0017740D FF D0 call eax 0017740F 3B F4 cmp esi,esp 00177411 E8 85 BA FF FF call __RTC_CheckEsp (0172E9Bh) 00177416 50 push eax 00177417 68 6C 2E 21 00 push offset string "%d\r\n" (0212E6Ch) 0017741C E8 A4 9F FF FF call _printf (01713C5h) 00177421 83 C4 08 add esp,8
(学习类的继承与多态后,还要学习更多相关内容。)Orz
来源:https://www.cnblogs.com/DirWang/p/12189053.html