对于虚函数,虽然一直知道怎么用,但是对其内部机制却不清楚。今天写了小段程序然后反汇编:
class CBase
{
public:
virtual void vfun(){};
void fun(){};
};
class CTest:public CBase
{
virtual void vfun(){};
void fun(){};
};
void main()
{
CTest pTest;
CBase *pBase;
pBase = &pTest;
pBase->fun();
pBase->vfun();
return;
}
对以上反汇编后的关键代码是:
void main()
{
013D1400 push ebp
013D1401 mov ebp,esp
013D1403 sub esp,0D8h
013D1409 push ebx
013D140A push esi
013D140B push edi
013D140C lea edi,[ebp-0D8h]
013D1412 mov ecx,36h
013D1417 mov eax,0CCCCCCCCh
013D141C rep stos dword ptr es:[edi]
CTest pTest;
013D141E lea ecx,[pTest]
013D1421 call CTest::CTest (13D11F9h) //13D11F9h是类CTest构造函数在内存中的地址
CBase *pBase;
pBase = &pTest;
013D1426 lea eax,[pTest]
013D1429 mov dword ptr [pBase],eax //这两句是将pTest的地址放入pBase代表的内存空间中
pBase->fun();
013D142C mov ecx,dword ptr [pBase]
013D142F call CBase::fun (13D118Bh) //这是调用的普通函数,直接调用的CBase的fun(13D118Bh是CBase的fun在内存中的地址)。
pBase->vfun();
013D1434 mov eax,dword ptr [pBase]
013D1437 mov edx,dword ptr [eax]
013D1439 mov esi,esp
013D143B mov ecx,dword ptr [pBase]
013D143E mov eax,dword ptr [edx]
013D1440 call eax //这一段时调用虚函数,可以看出是通过pTest的地址进行访问的,而pTest在代表的地址空间中存放的是一个虚函数表指针和内成员。
013D1442 cmp esi,esp
013D1444 call @ILT+330(__RTC_CheckEsp) (13D114Fh)
return;
}
结论:
类的成员函数是所有类共享的,是存放于代码段,而每个类的成员函数都有他特定的内存空间。当一个类实例化时,会分配一段内存空间存放成员变量,有意思的是当这个类有虚函数时,会默认的分配一个指针空间,该指针指向一个虚函数表。 当该实例进行函数调用时,如果是普通函数会直接调用该类的函数,如果是虚函数会通过该实例的指向虚函数表的指针进行对应的函数调用。
补充:
class CBase
{
public:
virtual void Test(int iTest = 0) const = 0;
};
class CDerived : public CBase
{
public:
void Test(int iTest = 1) const { cout << iTest << endl; };
};
void Test()
{
CBase *p = new CDerived;
p->Test();
delete p;
}
调试发现p->Test()虽然是调用的CDerived的,但是使用的缺省值却是CBase的。
所以缺省函数是属于静态绑定的。