首页 > 系统相关 >由多重继承导致的内存释放错误

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

时间:2023-02-05 18:11:06浏览次数:55  
标签:多重 68h 继承 text mov 内存 var rsp rax

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

问题提出

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个位置,然后再调用析构代理函数,所以内存释放就不会出现错误了

标签:多重,68h,继承,text,mov,内存,var,rsp,rax
From: https://www.cnblogs.com/czlnb/p/17093732.html

相关文章

  • brpc内存管理
    内存管理总是程序中的重要一环,在多线程时代,一个好的内存分配大都在如下两点间权衡:线程间竞争少。内存分配的粒度大都比较小,对性能敏感,如果不同的线程在大多数分配时会竞......
  • qtableview及自定义model的使用,对比qtablewidget性能及内存优化(转)
    以前一直使用qtableiwdget,最近有时间来研究下qtableview,才知道,qtableview和自定义model,比qtablewidget的性能啊,及占用内存啊,优化太多了。以前我使用qtablewidget是进行动态......
  • C/C++内存对齐原则
    C/C++内存对齐what&&why当用户自定义类型时(struct或class),编译器会自动计算该类型占用的字节数。C/C++为什么要内存对齐?我道行太浅,摘抄了网上的一个解释。为了方......
  • 5.4节约内存的编程方式
    以图形用户界面(GUI,GraphicalUserInterface)为基础的Windows,可以说是一个巨大的操作系统。Windows的前身是MS-DOS操作系统,最初版本可以在128KB左右的内存上运行,而想要W......
  • 内存和磁盘的亲密关系——5.1不读入内存就无法运行
    1.存储程序方式指的是什么?在存储装置中保存程序,并逐一运行的方式2.通过使用内存来提高磁盘访问速度的机制称为什么?DiskCache(磁盘缓存)3.把磁盘的一部分作为假想内存来......
  • 5.3虚拟内存把磁盘作为部分内存来使用
    接下来说一下虚拟内存虚拟内存是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。通过借助虚拟......
  • 熟练使用有棱有角的内存——4.1内存的物理机制很简单
    1.有十个地址信号引脚的内存IC(集成电路)可以指定的地址范围是多少?答:用二进制数来表示的话是0000000000~1111111111(用十进制数来表示的话是0~1023)2.高级编程语言中的数......
  • 4.2内存的逻辑模型是楼房
    虽然内存的实体是内存IC,不过从程序员的角度来看,也可以把它假想成每层都存储着数据的楼房,并不需要过多地关注内存IC的电源和控制信号等。因此,之后的讲解中我们也同样会使用......
  • 4.4数组是高效使用内存的基础
    数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。指定索引后,就可以对该索引所对应地......
  • C语言学习: 结构体的内存对齐
    怎么对齐的?是对齐他占用内存的倍数,看下面的代码intmain(){typedefstructPerson{char*name;intage;char*id;}Person;structPerson......