C++构造函数中不能使用多态

C++的构造函数被禁止使用多态

为什么C++要这么规定呢?

这是为了避免调用虚函数的派生类版本的时候,使用了一些未被初始化的字段,从而引发崩溃。

如何绕过这个限制
  1. 自定义Init,在构造函数执行完之后执行
  2. 委托初始化

从汇编分析

#include <iostream>

class A
{
public:
    A()
    {
        std::cout << "construction A" << std::endl;
        print();
    }
    
    virtual void print()
    {
        std::cout << "print a" << std::endl;
    }

private:
    int a = 1;
};

class B : public A
{
public:
    B()
    {
        std::cout << "construction B" << std::endl;
    }
    virtual void print()
    {
        std::cout << "print a" << std::endl;
    }
};

int main()
{
    A* p = new B;
    p->print();

    return 0;
}
int main()
{
004B68C0  push        ebp  
004B68C1  mov         ebp,esp  
004B68C3  push        0FFFFFFFFh  
004B68C5  push        4B7EA7h  
004B68CA  mov         eax,dword ptr fs:[00000000h]  
004B68D0  push        eax  
004B68D1  sub         esp,0E8h  
004B68D7  push        ebx  
004B68D8  push        esi  
004B68D9  push        edi  
004B68DA  lea         edi,[ebp-34h]  
004B68DD  mov         ecx,0Ah  
004B68E2  mov         eax,0CCCCCCCCh  
004B68E7  rep stos    dword ptr es:[edi]  
004B68E9  mov         eax,dword ptr [__security_cookie (04BC004h)]  
004B68EE  xor         eax,ebp  
004B68F0  push        eax  
004B68F1  lea         eax,[ebp-0Ch]  
004B68F4  mov         dword ptr fs:[00000000h],eax  
004B68FA  mov         ecx,offset _38199C8C_virtualTable@cpp (04BF029h)  
004B68FF  call        @__CheckForDebuggerJustMyCode@4 (04B140Bh)  
    A* p = new B;
004B6904  push        8                       //申请8字节地址空间
004B6906  call        operator new (04B1140h)  
004B690B  add         esp,4  
004B690E  mov         dword ptr [ebp-0ECh],eax    //返回值给局部变量
004B6914  mov         dword ptr [ebp-4],0  
004B691B  cmp         dword ptr [ebp-0ECh],0       
004B6922  je          __$EncStackInitStart+5Dh (04B6937h)  
004B6924  mov         ecx,dword ptr [ebp-0ECh]     //new出来的地址传给ecx
004B692A  call        B::B (04B1285h)              //B类的构造函数
004B692F  mov         dword ptr [ebp-0F4h],eax  
004B6935  jmp         __$EncStackInitStart+67h (04B6941h)  
004B6937  mov         dword ptr [ebp-0F4h],0  
004B6941  mov         eax,dword ptr [ebp-0F4h]  
004B6947  mov         dword ptr [ebp-0E0h],eax  
004B694D  mov         dword ptr [ebp-4],0FFFFFFFFh  
004B6954  mov         ecx,dword ptr [ebp-0E0h]  
004B695A  mov         dword ptr [p],ecx  
    p->print();
004B695D  mov         eax,dword ptr [p]  
004B6960  mov         edx,dword ptr [eax]  
004B6962  mov         esi,esp  
004B6964  mov         ecx,dword ptr [p]  
004B6967  mov         eax,dword ptr [edx]  
004B6969  call        eax  
004B696B  cmp         esi,esp  
004B696D  call        __RTC_CheckEsp (04B12F8h)  

    return 0;
004B6972  xor         eax,eax  
}
  1. new了8字节的地址空间
  2. new出来的地址传给ecx,调用B的构造函数
class B : public A
{
public:
    B()
004B2100  push        ebp  
004B2101  mov         ebp,esp  
004B2103  sub         esp,0CCh  
004B2109  push        ebx  
004B210A  push        esi  
004B210B  push        edi  
004B210C  push        ecx  
004B210D  lea         edi,[ebp-0Ch]  
004B2110  mov         ecx,3  
004B2115  mov         eax,0CCCCCCCCh  
004B211A  rep stos    dword ptr es:[edi]  
004B211C  pop         ecx  
004B211D  mov         dword ptr [this],ecx  //对象地址传给this
004B2120  mov         ecx,offset _38199C8C_virtualTable@cpp (04BF029h)  
004B2125  call        @__CheckForDebuggerJustMyCode@4 (04B140Bh)  
004B212A  mov         ecx,dword ptr [this]  //this传给ecx
004B212D  call        A::A (04B1474h)  		//调用A的构造函数
004B2132  mov         eax,dword ptr [this]  
004B2135  mov         dword ptr [eax],offset B::`vftable' (04B9B64h)  
    {
        std::cout << "construction B" << std::endl;
004B213B  mov         esi,esp  
004B213D  push        offset std::endl<char,std::char_traits<char> > (04B1041h)  
004B2142  push        offset string "construction B" (04B9B68h)  
004B2147  mov         eax,dword ptr [__imp_std::cout (04BD0D4h)]  
004B214C  push        eax  
004B214D  call        std::operator<<<std::char_traits<char> > (04B1203h)  
004B2152  add         esp,8  
004B2155  mov         ecx,eax  
004B2157  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04BD0A0h)]  
004B215D  cmp         esi,esp  
004B215F  call        __RTC_CheckEsp (04B12F8h)  
    }
004B2164  mov         eax,dword ptr [this]  
004B2167  pop         edi  
004B2168  pop         esi  
004B2169  pop         ebx  
004B216A  add         esp,0CCh  
004B2170  cmp         ebp,esp  
004B2172  call        __RTC_CheckEsp (04B12F8h)  
004B2177  mov         esp,ebp  
004B2179  pop         ebp  
004B217A  ret  
  1. 调用A的构造函数
class A
{
public:
    A()
004B2050  push        ebp  
004B2051  mov         ebp,esp  
004B2053  sub         esp,0CCh  
004B2059  push        ebx  
004B205A  push        esi  
004B205B  push        edi  
004B205C  push        ecx  
004B205D  lea         edi,[ebp-0Ch]  
004B2060  mov         ecx,3  
004B2065  mov         eax,0CCCCCCCCh  
004B206A  rep stos    dword ptr es:[edi]  
004B206C  pop         ecx  
004B206D  mov         dword ptr [this],ecx  //ecx传给this
004B2070  mov         ecx,offset _38199C8C_virtualTable@cpp (04BF029h)  
004B2075  call        @__CheckForDebuggerJustMyCode@4 (04B140Bh)  
004B207A  mov         eax,dword ptr [this]   //this 传给eax
004B207D  mov         dword ptr [eax],offset A::`vftable' (04B9B34h)  //将对象的虚表地址指向A类的虚表地址,虚表里的地址函数指向 A::print()
    
    virtual void print()
    {
        std::cout << "print a" << std::endl;
    }

private:
    int a = 1;
004B2083  mov         eax,dword ptr [this]  
004B2086  mov         dword ptr [eax+4],1     //初始化成员变量
    {
        std::cout << "construction A" << std::endl;
004B208D  mov         esi,esp  
004B208F  push        offset std::endl<char,std::char_traits<char> > (04B1041h)  
004B2094  push        offset string "construction A" (04B9B3Ch)  
004B2099  mov         eax,dword ptr [__imp_std::cout (04BD0D4h)]  
004B209E  push        eax  
004B209F  call        std::operator<<<std::char_traits<char> > (04B1203h)  
004B20A4  add         esp,8  
004B20A7  mov         ecx,eax  
004B20A9  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04BD0A0h)]  
004B20AF  cmp         esi,esp  
004B20B1  call        __RTC_CheckEsp (04B12F8h)  
        print();
004B20B6  mov         ecx,dword ptr [this]  
004B20B9  call        A::print (04B14B0h)    //这里注意没有从虚表中读取,编译器直接生成调用A::print
    }
  1. 将对象的虚表地址指向A类的虚表地址,虚表里的地址函数指向 A::print()
  2. 初始化成员变量
  3. 打印字符串
  4. 调用A::print (这里注意没有从虚表中读取,编译器直接生成调用A::print)
关键指令截取
int main()
004B6904  push        8                           //对象大小
004B6906  call        operator new (04B1140h)    //申请对象内存
004B690B  add         esp,4  
004B690E  mov         dword ptr [ebp-0ECh],eax  

004B6924  mov         ecx,dword ptr [ebp-0ECh]   //对象地址
004B692A  call        B::B (04B1285h)            //B的构造函数

B::B(void)
004B211D  mov         dword ptr [this],ecx    //this指针
004B212A  mov         ecx,dword ptr [this]    //对象地址
004B212D  call        A::A (04B1474h)         //A构造函数

A::A(void)
004B206D  mov         dword ptr [this],ecx    //this指针
004B207A  mov         eax,dword ptr [this]    //对象地址
004B207D  mov         dword ptr [eax],offset A::`vftable' (04B9B34h)    //对象的前四个字节指向A类的虚表

在这里插入图片描述
在这里插入图片描述
虚表的第一个函数就是A::print

A::A(void)
int a = 1;
004B2083  mov         eax,dword ptr [this]  
004B2086  mov         dword ptr [eax+4],1     //初始化成员变量

std::cout << "construction A" << std::endl;
.......

print();
004B20B6  mov         ecx,dword ptr [this]  
004B20B9  call        A::print (04B14B0h) //调用A::print (这里注意没有从虚表中读取,编译器直接生成调用A::print)


B::B(void)
004B212D  call        A::A (04B1474h)       //A构造函数,上面已经分析过了
004B2132  mov         eax,dword ptr [this]  
004B2135  mov         dword ptr [eax],offset B::`vftable' (04B9B64h) //对象的前四个字节指向B类的虚表

在这里插入图片描述
虚表中的第一个函数指向B::print()

B::B(void)
std::cout << "construction B" << std::endl;
............


int main()
004B6904  push        8                           //对象大小
004B6906  call        operator new (04B1140h)    //申请对象内存
004B690B  add         esp,4  
004B690E  mov         dword ptr [ebp-0ECh],eax  
........
004B692A  call        B::B (04B1285h)  

004B6954  mov         ecx,dword ptr [ebp-0E0h]  //对象地址
004B695A  mov         dword ptr [p],ecx  
    p->print();
004B695D  mov         eax,dword ptr [p]  
004B6960  mov         edx,dword ptr [eax]  //读取虚表地址
004B6964  mov         ecx,dword ptr [p]  //this 传给ecx
004B6967  mov         eax,dword ptr [edx]  //虚表中的第一个函数地址
004B6969  call        eax  
  1. 现在对象前4个字节指向的是B类的虚表
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值