环境:XPSP3 VS2005
今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码:
class Base { public: Base() { Fuction(); } virtual void Fuction() { cout << "Base::Fuction" << endl; } }; class A : public Base { public: A() { Fuction(); } virtual void Fuction() { cout << "A::Fuction" << endl; } }; // 这样定义一个A的对象,会输出什么? A a;
首先回答标题的问题,调用当然是没有问题的,但是获得的是你想要的结果吗?或者说你想要什么样的结果?
有人说会输出:
A::Fuction A::Fuction
Base::Fuction A::Fuction
据说在JAVA中是上一种输出(感觉有点匪夷所思)。
我们来单步看一下到底发生了什么?在A的构造函数里面首先会去调用Base的构造函数,Base的构造函数如下:
class Base
{
public:
Base()
00411600 push ebp
00411601 mov ebp,esp
00411603 sub esp,0CCh
00411609 push ebx
0041160A push esi
0041160B push edi
0041160C push ecx
0041160D lea edi,[ebp-0CCh]
00411613 mov ecx,33h
00411618 mov eax,0CCCCCCCCh
0041161D rep stos dword ptr es:[edi]
0041161F pop ecx
00411620 mov dword ptr [ebp-8],ecx
00411623 mov eax,dword ptr [this]
00411626 mov dword ptr [eax],offset Base::`vftable' (41770Ch)
{
Fuction();
0041162C mov ecx,dword ptr [this]
0041162F call Base::Fuction (4111A9h)
}
00411634 mov eax,dword ptr [this]
00411637 pop edi
00411638 pop esi
00411639 pop ebx
0041163A add esp,0CCh
00411640 cmp ebp,esp
00411642 call @ILT+460(__RTC_CheckEsp) (4111D1h)
00411647 mov esp,ebp
00411649 pop ebp
0041164A ret
从单步跟踪来看,注意黑色加粗的那部分汇编代码,ecx中存放的是对象的地址(0x0012ff60,我的机器上的情况看下图,有图有真相),首先是设置vtable的地址到对象的前四个字节(不同的编译器可能不同),然后就直接调用了Base::Fuction函数,并没有走虚机制,而我们此时看虚表中的状态,虚表已经填充的是0x4111a9,注意虚表的地址0x0041770c,而此时对象地址0x0012FF60前四个字节存放的正是0x0041770c。
继续跟踪,流程又回到A的构造函数中,再次注意加粗部分的代码,从基类Base的构造函数返回后,在A的构造函数中,重设了虚表指针,现在的虚表指针是(0x417700h),同样调用Fuction的时候直接调用了A::Fuction函数,并没有使用虚机制,而且此时虚表0x417700h指向的位置存放的0x41110e正是A::Fuction的地址。
class A : public Base
{
public:
A()
00411590 push ebp
00411591 mov ebp,esp
00411593 sub esp,0CCh
00411599 push ebx
0041159A push esi
0041159B push edi
0041159C push ecx
0041159D lea edi,[ebp-0CCh]
004115A3 mov ecx,33h
004115A8 mov eax,0CCCCCCCCh
004115AD rep stos dword ptr es:[edi]
004115AF pop ecx
004115B0 mov dword ptr [ebp-8],ecx
004115B3 mov ecx,dword ptr [this]
004115B6 call Base::Base (411140h)
004115BB mov eax,dword ptr [this]
004115BE mov dword ptr [eax],offset A::`vftable' (417700h)
{
Fuction();
004115C4 mov ecx,dword ptr [this]
004115C7 call A::Fuction (41110Eh)
}
004115CC mov eax,dword ptr [this]
004115CF pop edi
004115D0 pop esi
004115D1 pop ebx
004115D2 add esp,0CCh
004115D8 cmp ebp,esp
004115DA call @ILT+460(__RTC_CheckEsp) (4111D1h)
004115DF mov esp,ebp
004115E1 pop ebp
004115E2 ret
其实事情就是这么简单。
本文转自:http://blog.csdn.net/magictong/article/details/6734241