• C++中构造函数能调用虚函数吗?(答案是语法可以,输出错误),但Java里居然可以


          环境:XPSP3 VS2005

            今天黑总给应聘者出了一个在C++的构造函数中调用虚函数的问题,具体的题目要比标题复杂,大体情况可以看如下的代码:

    [cpp] view plain copy
     
    1. class Base  
    2. {  
    3. public:  
    4.     Base()  
    5.     {  
    6.         Fuction();  
    7.     }  
    8.   
    9.     virtual void Fuction()  
    10.     {  
    11.         cout << "Base::Fuction" << endl;  
    12.     }  
    13. };  
    14.   
    15. class A : public Base  
    16. {  
    17. public:  
    18.     A()  
    19.     {  
    20.         Fuction();  
    21.     }  
    22.   
    23.     virtual void Fuction()  
    24.     {  
    25.         cout << "A::Fuction" << endl;  
    26.     }  
    27. };  
    28.   
    29. // 这样定义一个A的对象,会输出什么?  
    30. A a;  

            首先回答标题的问题,调用当然是没有问题的,但是获得的是你想要的结果吗?或者说你想要什么样的结果?

            有人说会输出:

    [html] view plain copy
     
    1. A::Fuction  
    2. A::Fuction  

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

    [html] view plain copy
     
    1. Base::Fuction  
    2. 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

  • 相关阅读:
    serial number
    python getopt
    python readline,seek
    linux scp
    jenkinsapi
    windows kill process
    python time
    python configparse
    解决某些.net不方便解决的问题,解决方法就是 DHTML
    (转)windows XP 系统服务“关闭”详细列表,释放N多内存,128也够用了!
  • 原文地址:https://www.cnblogs.com/findumars/p/7123497.html
Copyright © 2020-2023  润新知