环境: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;
首先回答标题的问题,调用当然是没有问题的,但是获得的是你想要的结果吗?或者说你想要什么样的结果?
有人说会输出:
如果是这样,首先我们回顾下C++对象模型里面的构造顺序,在构造一个子类对象的时候,首先会构造它的基类,如果有多层继承关系,实际上会从最顶层的基类逐层往下构造(虚继承、多重继承这里不讨论),如果是按照上面的情形进行输出的话,那就是说在构造Base的时候,也就是在Base的构造函数中调用Fuction的时候,调用了子类A的Fuction,而实际上A还没有开始构造,这样函数的行为就是完全不可预测的,因此显然不是这样,实际的输出结果是:
据说在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
|
请发表评论