• 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    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类的虚表
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    在这里插入图片描述
    虚表的第一个函数就是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类的虚表
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    虚表中的第一个函数指向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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1. 现在对象前4个字节指向的是B类的虚表
  • 相关阅读:
    数学工程学|正态分布及其图形
    深度学习之环境配置 jupyter notebook
    win11怎么把c盘移到d盘?
    java封装,继承,多态
    二叉树的后续遍历(迭代法)
    docker方式启动一个java项目-Nginx本地有代码,并配置反向代理
    C++学习——前进(三)
    es请求方式调用
    11、视频分类建议
    使用ORDER BY 排序
  • 原文地址:https://blog.csdn.net/ly1390811049/article/details/125493494