using namespace std;
class B
{
public:
virtual void fun(){cout<<"B::fun()"<<endl;}
};
int main()
{
B b;
// 第一种方式(查表)
B* p = &b;
p->fun();
// 第二种方式(调整函数)
void (B::*pf)();
pf = &B::fun;
(b.*pf)();
cin.get();
return 0;
}
// 第一种方式(查表)
B* p = &b;
00411536 lea eax,[b]
00411539 mov dword ptr [p],eax
p->fun();
0041153C mov eax,dword ptr [p] 将对象地址传入eax
0041153F mov edx,dword ptr [eax] 将虚函数表地址传入edx
00411541 mov esi,esp
00411543 mov ecx,dword ptr [p]
00411546 mov eax,dword ptr [edx] 调用虚函数表的第一项(也就是第一个虚函数)
00411548 call eax
// 第二种方式(调整函数)
void (B::*pf)();
pf = &B::fun;
00411551 mov dword ptr [pf],offset B::`vcall'{0}' (411285h)
(b.*pf)();
00411558 mov esi,esp
0041155A lea ecx,[b] 将对象地焉传入ECX
0041155D call dword ptr [pf] 调用调整函数
以下是调整函数内容
00411285 jmp B::`vcall'{0}' (4115C0h)
B::`vcall'{0}':
004115C0 mov eax,dword ptr [ecx] 将虚函数表存入eax
004115C2 jmp dword ptr [eax] 跳转到虚函数表的第一条记录处
00411023 jmp B::fun (411620h)
这是DEBUG模式下的汇编所以,所有多了一些JMP指令
看得出第一种是通过查表的方式,得出虚函数地址调用
第二种是通过将函数指针的值赋给一个调整函数调用调整函数,在调整函数中查表,然后调用
在DEBUG模式下,多继承的情况下,用指针调用继承自两个基类的虚函数,传入的ECX居然是后一个类的地址。通过在与后一个类的位置进行正负计算得出成员的位置