您的位置:首页 > 编程语言 > C语言/C++

C++构造函数调用虚函数的问题

2013-07-25 12:16 447 查看
环境: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
如果是这样,首先我们回顾下C++对象模型里面的构造顺序,在构造一个子类对象的时候,首先会构造它的基类,如果有多层继承关系,实际上会从最顶层的基类逐层往下构造(虚继承、多重继承这里不讨论),如果是按照上面的情形进行输出的话,那就是说在构造Base的时候,也就是在Base的构造函数中调用Fuction的时候,调用了子类A的Fuction,而实际上A还没有开始构造,这样函数的行为就是完全不可预测的,因此显然不是这样,实际的输出结果是:

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

其实事情就是这么简单。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: