由多重继承导致的内存释放错误

由多重继承导致的内存释放错误

问题提出

C++中的delete expression或者(delete operator)在多继承下,如果使用不正确的话,可能存在程序崩溃的情况。如下代码所示:

// main.cpp
class Base1
{
    int mem_b1;
public:
    Base1() : mem_b1(0) {}
    ~Base1() {}
};

class Base2
{
    int mem_b2;
public:
    Base2() : mem_b2(0) {}
    ~Base2() {}
};

class Derived : public Base1, public Base2
{
    int mem_d;
public:
    Derived() : mem_d(0) {}
    ~Derived() {}
};

int main()
{
    Base2 *b2 = new Derived();
    delete b2;
}                              

上面的代码在delete b2;就会报错,错误信息如下

错误原因

错误原因是因为对象上转型的过程中指针向后移动了,而析构的时候没有将指针移回来,由于创建动态内存的指针和释放动态内存的指针不相同,而导致无效指针错误

main()

.text:00000001400015B0                 push    rdi
.text:00000001400015B2                 sub     rsp, 60h
.text:00000001400015B6                 mov     ecx, 0Ch        ; size
.text:00000001400015BB                 call    j_??2@YAPEAX_K@Z ; operator new(unsigned __int64)
.text:00000001400015C0                 mov     [rsp+68h+var_38], rax
.text:00000001400015C5                 cmp     [rsp+68h+var_38], 0
.text:00000001400015CB                 jz      short loc_1400015DE
.text:00000001400015CD                 mov     rcx, [rsp+68h+var_38] ; this
.text:00000001400015D2                 call    j_??0Derived@@QEAA@XZ ; Derived::Derived(void)
.text:00000001400015D7                 mov     [rsp+68h+var_28], rax
.text:00000001400015DC                 jmp     short loc_1400015E7
.text:00000001400015DE ; ---------------------------------------------------------------------------
.text:00000001400015DE
.text:00000001400015DE loc_1400015DE:                          ; CODE XREF: main+1B↑j
.text:00000001400015DE                 mov     [rsp+68h+var_28], 0
.text:00000001400015E7
.text:00000001400015E7 loc_1400015E7:                          ; CODE XREF: main+2C↑j
.text:00000001400015E7                 mov     rax, [rsp+68h+var_28]
.text:00000001400015EC                 mov     [rsp+68h+var_40], rax
.text:00000001400015F1                 cmp     [rsp+68h+var_40], 0
.text:00000001400015F7                 jz      short loc_140001609
.text:00000001400015F9                 mov     rax, [rsp+68h+var_40]
.text:00000001400015FE                 add     rax, 4	//指针向后调整4个位置
.text:0000000140001602                 mov     [rsp+68h+var_20], rax
.text:0000000140001607                 jmp     short loc_140001612
.text:0000000140001609 ; ---------------------------------------------------------------------------
.text:0000000140001609
.text:0000000140001609 loc_140001609:                          ; CODE XREF: main+47↑j
.text:0000000140001609                 mov     [rsp+68h+var_20], 0
.text:0000000140001612
.text:0000000140001612 loc_140001612:                          ; CODE XREF: main+57↑j
.text:0000000140001612                 mov     rax, [rsp+68h+var_20]
.text:0000000140001617                 mov     [rsp+68h+var_48], rax
.text:000000014000161C                 mov     rax, [rsp+68h+var_48]
.text:0000000140001621                 mov     [rsp+68h+var_30], rax
.text:0000000140001626                 cmp     [rsp+68h+var_30], 0
.text:000000014000162C                 jz      short loc_140001644
.text:000000014000162E                 mov     edx, 1          ; unsigned int
.text:0000000140001633                 mov     rcx, [rsp+68h+var_30] ; this
.text:0000000140001638                 call    j_??_GBase2@@QEAAPEAXI@Z ; Base2::`scalar deleting destructor'(uint)
.text:000000014000163D                 mov     [rsp+68h+var_18], rax
.text:0000000140001642                 jmp     short loc_14000164D
.text:0000000140001644 ; ---------------------------------------------------------------------------
.text:0000000140001644
.text:0000000140001644 loc_140001644:                          ; CODE XREF: main+7C↑j
.text:0000000140001644                 mov     [rsp+68h+var_18], 0
.text:000000014000164D
.text:000000014000164D loc_14000164D:                          ; CODE XREF: main+92↑j
.text:000000014000164D                 xor     eax, eax
.text:000000014000164F                 add     rsp, 60h
.text:0000000140001653                 pop     rdi
.text:0000000140001654                 retn

Base2::`scalar deleting destructor'(uint)

.text:0000000140001730 arg_0           = qword ptr  8
.text:0000000140001730 arg_8           = dword ptr  10h
.text:0000000140001730
.text:0000000140001730                 mov     [rsp+arg_8], edx
.text:0000000140001734                 mov     [rsp+arg_0], rcx
.text:0000000140001739                 push    rdi
.text:000000014000173A                 sub     rsp, 20h
.text:000000014000173E                 mov     rcx, [rsp+28h+arg_0] ; this
.text:0000000140001743                 call    j_??1Base2@@QEAA@XZ ; Base2::~Base2(void)
.text:0000000140001748                 mov     eax, [rsp+28h+arg_8]
.text:000000014000174C                 and     eax, 1
.text:000000014000174F                 test    eax, eax
.text:0000000140001751                 jz      short loc_140001762
.text:0000000140001753                 mov     edx, 4          ; __formal
.text:0000000140001758                 mov     rcx, [rsp+28h+arg_0] ; block
.text:000000014000175D                 call    j_??3@YAXPEAX_K@Z ; operator delete(void *,unsigned __int64)
.text:0000000140001762
.text:0000000140001762 loc_140001762:                          ; CODE XREF: Base2::`scalar deleting destructor'(uint)+21↑j
.text:0000000140001762                 mov     rax, [rsp+28h+arg_0]
.text:0000000140001767                 add     rsp, 20h
.text:000000014000176B                 pop     rdi
.text:000000014000176C                 retn

如上所示,在main函数中,在对象上转型的过程中,指针向后移动了4,但是在Base2::scalar deleting destructor(uint)却没有调整回来,直接调用了delete函数,导致因创建和释放内存的位置不一样而错误

解决办法

将所有析构函数都变成虚函数。

为什么这样可以解决问题呢,我们加上virtual 后再看看汇编代码

main()

.text:0000000140001600 ; __unwind { // __CxxFrameHandler4_0
.text:0000000140001600                 push    rdi
.text:0000000140001602                 sub     rsp, 60h
.text:0000000140001606                 mov     ecx, 28h ; '('  ; size
.text:000000014000160B                 call    j_??2@YAPEAX_K@Z ; operator new(unsigned __int64)
.text:0000000140001610                 mov     [rsp+68h+var_38], rax
.text:0000000140001615                 cmp     [rsp+68h+var_38], 0
.text:000000014000161B                 jz      short loc_14000162E
.text:000000014000161D                 mov     rcx, [rsp+68h+var_38] ; this
.text:0000000140001622                 call    j_??0Derived@@QEAA@XZ ; Derived::Derived(void)
.text:0000000140001627                 mov     [rsp+68h+var_28], rax
.text:000000014000162C                 jmp     short loc_140001637
.text:000000014000162E ; ---------------------------------------------------------------------------
.text:000000014000162E
.text:000000014000162E loc_14000162E:                          ; CODE XREF: main+1B↑j
.text:000000014000162E                 mov     [rsp+68h+var_28], 0
.text:0000000140001637
.text:0000000140001637 loc_140001637:                          ; CODE XREF: main+2C↑j
.text:0000000140001637                 mov     rax, [rsp+68h+var_28]
.text:000000014000163C                 mov     [rsp+68h+var_40], rax
.text:0000000140001641                 cmp     [rsp+68h+var_40], 0
.text:0000000140001647                 jz      short loc_140001659
.text:0000000140001649                 mov     rax, [rsp+68h+var_40]
.text:000000014000164E                 add     rax, 10h // 整了0x10个位置
.text:0000000140001652                 mov     [rsp+68h+var_20], rax
.text:0000000140001657                 jmp     short loc_140001662
.text:0000000140001659 ; ---------------------------------------------------------------------------
.text:0000000140001659
.text:0000000140001659 loc_140001659:                          ; CODE XREF: main+47↑j
.text:0000000140001659                 mov     [rsp+68h+var_20], 0
.text:0000000140001662
.text:0000000140001662 loc_140001662:                          ; CODE XREF: main+57↑j
.text:0000000140001662                 mov     rax, [rsp+68h+var_20]
.text:0000000140001667                 mov     [rsp+68h+var_48], rax
.text:000000014000166C                 mov     rax, [rsp+68h+var_48]
.text:0000000140001671                 mov     [rsp+68h+var_30], rax
.text:0000000140001676                 cmp     [rsp+68h+var_30], 0
.text:000000014000167C                 jz      short loc_140001699
.text:000000014000167E                 mov     rax, [rsp+68h+var_30]
.text:0000000140001683                 mov     rax, [rax]
.text:0000000140001686                 mov     edx, 1
.text:000000014000168B                 mov     rcx, [rsp+68h+var_30]
.text:0000000140001690                 call    qword ptr [rax] // 用的是虚表的第一个函数
.text:0000000140001692                 mov     [rsp+68h+var_18], rax
.text:0000000140001697                 jmp     short loc_1400016A2
.text:0000000140001699 ; ---------------------------------------------------------------------------
.text:0000000140001699
.text:0000000140001699 loc_140001699:                          ; CODE XREF: main+7C↑j
.text:0000000140001699                 mov     [rsp+68h+var_18], 0
.text:00000001400016A2
.text:00000001400016A2 loc_1400016A2:                          ; CODE XREF: main+97↑j
.text:00000001400016A2                 xor     eax, eax
.text:00000001400016A4                 add     rsp, 60h
.text:00000001400016A8                 pop     rdi
.text:00000001400016A9                 retn

上图,指针向后调整0x10个位置后,调用构造析构函数,调用的是虚表的第一个函数,我们看看构造函数,看看虚表是怎么被赋值的

Derived::Derived(void)

.text:0000000140001760                 mov     [rsp+arg_0], rcx
.text:0000000140001765                 push    rdi
.text:0000000140001766                 sub     rsp, 20h
.text:000000014000176A                 mov     rcx, [rsp+28h+arg_0] ; this
.text:000000014000176F                 call    j_??0Base1@@QEAA@XZ ; Base1::Base1(void)
.text:0000000140001774                 nop
.text:0000000140001775                 mov     rax, [rsp+28h+arg_0]
.text:000000014000177A                 add     rax, 10h
.text:000000014000177E                 mov     rcx, rax        ; this
.text:0000000140001781                 call    j_??0Base2@@QEAA@XZ ; Base2::Base2(void)
.text:0000000140001786                 mov     rax, [rsp+28h+arg_0]
.text:000000014000178B                 lea     rcx, ??_7Derived@@6BBase1@@@ ; const Derived::`vftable'{for `Base1'}
.text:0000000140001792                 mov     [rax], rcx
.text:0000000140001795                 mov     rax, [rsp+28h+arg_0]
.text:000000014000179A                 lea     rcx, ??_7Derived@@6BBase2@@@ ; const Derived::`vftable'{for `Base2'}
.text:00000001400017A1                 mov     [rax+10h], rcx
.text:00000001400017A5                 mov     rax, [rsp+28h+arg_0]
.text:00000001400017AA                 mov     dword ptr [rax+20h], 0
.text:00000001400017B1                 mov     rax, [rsp+28h+arg_0]
.text:00000001400017B6                 add     rsp, 20h
.text:00000001400017BA                 pop     rdi
.text:00000001400017BB                 retn

从上面可知,调用的虚表应该是const Derived::vftable'{for Base2'}

查看const Derived::vftable'{for Base2'}

虚表只有一个函数,我们查看这个函数

.text:000000014000187C ; [thunk]:public: virtual void * Derived::`vector deleting destructor'`adjustor{16}' (unsigned int)
.text:000000014000187C ??_EDerived@@WBA@EAAPEAXI@Z proc near   ; CODE XREF: [thunk]:Derived::`vector deleting destructor'`adjustor{16}' (uint)↑j
.text:000000014000187C                 sub     rcx, 10h        ; this
.text:0000000140001880                 jmp     j_??_GDerived@@UEAAPEAXI@Z_0 ; Derived::`scalar deleting destructor'(uint)
.text:0000000140001880 ??_EDerived@@WBA@EAAPEAXI@Z endp

看到这里应该明白了,上面向前调整了0x10个位置,然后再调用析构代理函数,所以内存释放就不会出现错误了

posted @ 2023-02-05 18:03  乘舟凉  阅读(234)  评论(0编辑  收藏  举报