C++ new和delete的原理分析

前言

Effective C++ rule 16规则,让我重新认识了delete与new在处理数组时候的方式。new 有两种形式的new,一种是生成一个对象的operator New,另一个是用于数组的operator new []。同时 operator delete也分普通版本的operator delete 以及数组版的operator delete[].

先说结论系列

1.Operator new[]的工作原理是会使用malloc函数一次性申请好此次操作+4个字节大小的内存空间,其所申请的内存结构如下:

数组元素个数n对象1对象2对象3·······对象n-1对象n

图1 operator new[]返回值的内存布局

然后依据本次申请的对象数,循环调用数组内各个对象的构造函数。

2.Operator new 的工作原理没有什么好说的,就是malloc对象所需大小的内存,然后调用对象的构造函数。它并没有在内存中记录数组的对象个数。

3.delete []的工作原理是先把参数向后偏移4个字节,得到后续元素个数,然后在一个循环中依次调用各元素的析构函数,最后再调用free()函数,其中传递给free的是偏移之后的地址。

4.delete 的工作原理很简单,调用参数的析构函数,然后在把这个地址无需偏移传递给free.

5.operator new [] 与delete[]成对出现;operator new 与delete成对出现。

Talk is cheap, show my code.

#include <stdio.h>
#include <stdlib.h>
#include <memory>
using namespace std;
class Super
{
public:
    Super()
    {
        printf("%d Super()\n",this);
        c = (int)this;
    }
    ~Super()
    {
        printf("%d ~Super\n", this);
    }
public:
    int c;
};
int main()
{
    Super * sp = new Super[15];
    Super * sp1 = new Super;
    //printf("%d\n", *((char*)((int)sp1-4)));
    delete[] sp;
    delete sp1;
    //delete sp1;
    system("pause");
    return 0;
}

我们希望看到有15次 Super构造函数的调用,以及15次析构函数的调用。
运行结果:

6374692 Super()
6374696 Super()
6374700 Super()
6374704 Super()
6374708 Super()
6374712 Super()
6374716 Super()
6374720 Super()
6374724 Super()
6374728 Super()
6374732 Super()
6374736 Super()
6374740 Super()
6374744 Super()
6374748 Super()
6380784 Super()
6374748 ~Super
6374744 ~Super
6374740 ~Super
6374736 ~Super
6374732 ~Super
6374728 ~Super
6374724 ~Super
6374720 ~Super
6374716 ~Super
6374712 ~Super
6374708 ~Super
6374704 ~Super
6374700 ~Super
6374696 ~Super
6374692 ~Super
6380784 ~Super

有15次构造函数的调用,15次析构函数的调用。这次正常的,我们所想要的。
看汇编代码:
从Super *sp =new Super[15]开始,我们看汇编代码:

00BB261D  push        40h  将40H作为参数压入栈,注意40H=64=15*4+4.
00BB261F  call        operator new[] (0BB10C8h)  首先会调用operator new []函数,参数是40H.
    我们看这个 operator new []函数:
operator new[]:
00BB10C8  jmp         operator new[] (0BB28D0h)跳转到真正的入口 
     4: void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
     5:     {   // try to allocate count bytes for an array
00BB28D0  push        ebp  
00BB28D1  mov         ebp,esp  
     6:     return (operator new(count));
00BB28D3  mov         eax,dword ptr [count]  
00BB28D6  push        eax  
00BB28D7  call        operator new (0BB11BDh)  调用operaotr new ,参数是eax=40H
    我们再看0B11BDH处的operator new ,可以发现那就是类似于malloc的过程。
00BB28DC  add         esp,4  
     7:     }
00BB2624  add         esp,4  
00BB2627  mov         dword ptr [ebp-11Ch],eax  eax是operator new []的返回值
00BB262D  mov         dword ptr [ebp-4],0  
00BB2634  cmp         dword ptr [ebp-11Ch],0  比较是否为NULL,以判断申请内存是否成功
00BB263B  je          main+97h (0BB2677h)  
00BB263D  mov         eax,dword ptr [ebp-11Ch]  
00BB2643  mov         dword ptr [eax],0Fh  往0BB28D0H的operator new[]返回值所在的内存写入0FH(15).而eax其实就是malloc的返回值, 是申请内存块的起始处。首先往内存块的起始处写入后面紧跟的元素的个数。这个起始的4个字节就是图1 最左侧的“数组元素个数”
00BB2649  push        0BB1087h  将~Super()的地址压栈
00BB264E  push        0BB1023h  将Super()的地址压栈
00BB2653  push        0Fh      将元素个数压栈
00BB2655  push        4            将每个元素的大小压栈
00BB2657  mov         ecx,dword ptr [ebp-11Ch]  
00BB265D  add         ecx,4  
00BB2660  push        ecx      将malloc返回值往高地址偏移4个字节入栈,这其实就是第一人数组元素的地址 
00BB2661  call        `eh vector constructor iterator' (0BB1145h)  调用迭代构造器 
再看迭代构造器:
`eh vector constructor iterator':
00BB28F0  push        ebp  
00BB28F1  mov         ebp,esp  
00BB28F3  push        0FFFFFFFEh  
00BB28F5  push        0BBA200h  
00BB28FA  push        0BB108Ch  
00BB28FF  mov         eax,dword ptr fs:[00000000h]  
00BB2905  push        eax  
00BB2906  add         esp,0FFFFFFF0h  
00BB2909  push        ebx  
00BB290A  push        esi  
00BB290B  push        edi  
00BB290C  mov         eax,dword ptr ds:[00BBB000h]  
00BB2911  xor         dword ptr [ebp-8],eax  
00BB2914  xor         eax,ebp  
00BB2916  push        eax  
00BB2917  lea         eax,[ebp-10h]  
00BB291A  mov         dword ptr fs:[00000000h],eax  
00BB2920  mov         dword ptr [i],0  
00BB2927  mov         dword ptr [success],0  
00BB292E  mov         dword ptr [ebp-4],0  
00BB2935  jmp         `eh vector constructor iterator'+50h (0BB2940h)  
00BB2937  mov         eax,dword ptr [i]  
00BB293A  add         eax,1  
00BB293D  mov         dword ptr [i],eax  
00BB2940  mov         ecx,dword ptr [i]  
00BB2943  cmp         ecx,dword ptr [count]   以上几行形成一个循环控制结构
00BB2946  jge         `eh vector constructor iterator'+69h (0BB2959h)  
00BB2948  mov         ecx,dword ptr [ptr]  
00BB294B  call        dword ptr [pCtor]  调用Super()
00BB294E  mov         edx,dword ptr [ptr]  
00BB2951  add         edx,dword ptr [size]  
00BB2954  mov         dword ptr [ptr],edx  偏移到下一个对象
00BB2957  jmp         `eh vector constructor iterator'+47h (0BB2937h)  
00BB2959  mov         dword ptr [success],1  全部调用完构造函数
00BB2960  mov         dword ptr [ebp-4],0FFFFFFFEh  
00BB2967  call        `eh vector constructor iterator'+7Eh (0BB296Eh)  
00BB296C  jmp         $LN12 (0BB298Ah)  
$LN11:
00BB296E  cmp         dword ptr [success],0  
00BB2972  jne         `eh vector constructor iterator'+99h (0BB2989h)  
00BB2974  mov         eax,dword ptr [pDtor]  
00BB2977  push        eax  
00BB2978  mov         ecx,dword ptr [i]  
00BB297B  push        ecx  
00BB297C  mov         edx,dword ptr [size]  
00BB297F  push        edx  
00BB2980  mov         eax,dword ptr [ptr]  
00BB2983  push        eax  
00BB2984  call        __ArrayUnwind (0BB11EAh)  
$LN13:
00BB2989  ret  
$LN12:
00BB298A  mov         ecx,dword ptr [ebp-10h]  
00BB298D  mov         dword ptr fs:[0],ecx  
00BB2994  pop         ecx  
00BB2995  pop         edi  
00BB2996  pop         esi  
00BB2997  pop         ebx  
00BB2998  mov         esp,ebp  
00BB299A  pop         ebp  
00BB299B  ret         14h  
00BB2666  mov         edx,dword ptr [ebp-11Ch]  
00BB266C  add         edx,4  
00BB266F  mov         dword ptr [ebp-130h],edx  
00BB2675  jmp         main+0A1h (0BB2681h)  
00BB2677  mov         dword ptr [ebp-130h],0  
00BB2681  mov         eax,dword ptr [ebp-130h]  
00BB2687  mov         dword ptr [ebp-128h],eax  
00BB268D  mov         dword ptr [ebp-4],0FFFFFFFFh  
00BB2694  mov         ecx,dword ptr [ebp-128h]  
    22:     Super * sp = new Super[15];
00BB269A  mov         dword ptr [sp],ecx  最后将偏移后的指针返回给sp.

于是我们知道Super *sp = new Super[15]的过程,使用伪代码表示是 :

count =15;
size =sizeof(Super)
void * _p =malloc(Count*Size+4)
void * tp =(void *)(((char*)p)+4)
for I in 0..15:
    tp=(void*)(((char*)tp)+Size*I)
    ((Super*)tp)->Super();
*(int*)_p=Count
sp =(Super*)((int*)_p+1);

我们再来 Super *sp1 =new Super;的汇编代码

    23:     Super * sp1 = new Super;
012C269D  push        4  
012C269F  call        operator new (012C11BDh)  只申请4个字节
012C26A4  add         esp,4  
012C26A7  mov         dword ptr [ebp-11Ch],eax  
012C26AD  mov         dword ptr [ebp-4],1  
012C26B4  cmp         dword ptr [ebp-11Ch],0  
012C26BB  je          main+0F0h (012C26D0h)  
012C26BD  mov         ecx,dword ptr [ebp-11Ch]  
012C26C3  call        Super::Super (012C1023h)  调用构造函数
012C26C8  mov         dword ptr [ebp-148h],eax  
012C26CE  jmp         main+0FAh (012C26DAh)  
012C26D0  mov         dword ptr [ebp-148h],0  
012C26DA  mov         eax,dword ptr [ebp-148h]  
012C26E0  mov         dword ptr [ebp-128h],eax  
012C26E6  mov         dword ptr [ebp-4],0FFFFFFFFh  
012C26ED  mov         ecx,dword ptr [ebp-128h]  
012C26F3  mov         dword ptr [sp1],ecx       返回operator的值

于是我们知道Super * sp1 =new Super的实际过程的伪代码为:

    void *_p =malloc(sizeof(Super))
    ((Super*)_p)->Super();
    sp1 =(Super*)_p;

通过查看汇编代码,我们可以知道 delete[] sp;的过程是:

count =*(((int*)sp)-1)
for i in 0..count:
    (Sp+i)->~Super()
free(void*(((int*)sp)-1))

而delete sp1的过程就简单的多了:

    (sp1)->~Super()
    free(sp1)

如果···

1.如果delete sp会发生什么呢?


分析:

Sp指向的是数组内存块的第一个元素的起始地址,但它不是整个动态内存块的起始处,换句话说,sp不是某次malloc的返回值,因而它无法被free处理,因为free只接受malloc一系列动态分配函数的返回值。目测,会有第一个~Super()会被调用,然后立马程序会崩溃。因为它执行了类似于下面的语句:
    (sp)->~Super()
    free(sp);GG!!

测试代码:

类定义与上面一致,修改main():
int main()
{
    Super * sp = new Super[15];
    Super * sp1 = new Super;
    //printf("%d\n", *((char*)((int)sp1-4)));
    delete sp;
    delete sp1;
    //delete sp1;
    system("pause");
    return 0;
}

看运行结果:


14828836 Super()
14828840 Super()
14828844 Super()
14828848 Super()
14828852 Super()
14828856 Super()
14828860 Super()
14828864 Super()
14828868 Super()
14828872 Super()
14828876 Super()
14828880 Super()
14828884 Super()
14828888 Super()
14828892 Super()
14849392 Super()
14828836 ~Super
Open 052450C8 error!

的确如此,我们的分析是正确的。

2.如果delete[] sp1会怎么样?

分析:


delete [] sp1时会执行类似于下面的语句:
    count =*(((int*)sp)-1)      :Unknown value,未知的值
    for i in 0..count:  
        (Sp+i)->~Super()       如果Count不为0,那么至少会调用一次~Super()
    free(void*(((int*)sp)-1))   GG!奔溃

测试代码:

类定义与上面一致,修改main():
int main()
{
    //Super * sp = new Super[15];
    Super * sp1 = new Super;
    //printf("%d\n", *((char*)((int)sp1-4)));
    //delete[] sp;
    delete[] sp1;
    //delete sp1;
    system("pause");
    return 0;
}

运行结果:

10437920 Super()
Open 04E850C8 error!

果然GG

总结:

1.Super *sp = new Super[15]的过程,使用伪代码表示是 :

    count =15;
    size =sizeof(Super)
    void * _p =malloc(Count*Size+4)
    void * tp =(void *)(((char*)p)+4)
    for I in 0..15:
        tp=(void*)(((char*)tp)+Size*I)
        ((Super*)tp)->Super();
    *(int*)_p=Count
    Sp =(Super*)((int*)_p+1);

2. Super * sp1 =new Super的实际过程的伪代码为:

    void *_p =malloc(sizeof(Super))
    ((Super*)_p)->Super();
    sp1 =(Super*)_p;

3.delete[] sp;的过程是:

    count =*(((int*)sp)-1)
    for I in 0..count:
        (Sp+i)->~Super()
    free(void*(((int*)sp)-1))

4.delete sp1的过程就简单的多了

    (sp1)->~Super()
    free(sp1)

5.delete与new要使用相同的形式

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值