前言

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=CountSp =(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要使用相同的形式

C++ new和delete的原理分析相关推荐

  1. Glide DiskCache 原理分析

    Glide DiskCache 原理分析 1.感性认识DiskCache DiskCache默认设置: a.内部存储位置 内部存储位置: /data/{package}/cache/image_man ...

  2. Elasticsearch实现原理分析

    介绍 本文是分析Elasticsearch系列文章中的一篇,是一个译文.共有三个部分,每部分讲解部分Elasticsearch的实现原理.     在翻译的过程中,也需要查看对应部分的源码,来加深对实 ...

  3. 数据结构 练习21-trie的原理分析和应用

    前言 今天具体分析一下trie树,包括:原理分析,应用场合,复杂度分析,与hash的比较,源码展现.大部分内容来自互联网,文中会注明出处. 原理分析 主要是hash树的变种,先看下图: 每一个点存储一 ...

  4. c++ map 获取key列表_好未来Golang源码系列一:Map实现原理分析

    分享老师:学而思网校 郭雨田 一.map的结构与设计原理 golang中map是一个kv对集合.底层使用hash table,用链表来解决冲突 ,出现冲突时,不是每一个key都申请一个结构通过链表串起 ...

  5. MyBatis 动态 SQL 底层原理分析

    MyBatis 动态 SQL 底层原理分析 我们在使用mybatis的时候,会在xml中编写sql语句. 比如这段动态sql代码: <update id="update" p ...

  6. 第 3 章 MybatisPlus 注入 SQL 原理分析

    第 3 章 MybatisPlus 注入 SQL 原理分析 思考问题 我们编写的 UserMapper 继承了 BaseMapper<T>,就拥有了基本的增删改查功能,这是因为 BaseM ...

  7. 一图抵千言《ARouter简明扼要原理分析》

    配置 Kotlin项目: module App: apply plugin: 'kotlin-kapt'defaultConfig{javaCompileOptions {annotationProc ...

  8. 安卓手机Recovery概述和原理分析

    安卓手机Recovery概述 1.Recovery是用户想要刷机的过程中经常会遇到的一个词.那么什么是Recovery?Recovery模式又是什么意思?手机怎么进入Recovery模式? 2.Rec ...

  9. RPC 实战与核心原理分析

    RPC 实战与核心原理分析 RPCX是一个分布式的Go语言的 RPC 框架,支持Zookepper.etcd.consul多种服务发现方式,多种服务路由方式, 例子 服务端 package maini ...

最新文章

  1. 提高智能家居设备的兼容性
  2. 整理Java基础知识--输出格式知识
  3. Python 中 zip() 函数的用法
  4. 谷歌用量子计算机造出「时间晶体」,挑战热力学第二定律
  5. 【毕设进行时-工业大数据,数据挖掘】Java GUI完善,左对齐
  6. mysql qps计算方法_mysql计算 TPS,QPS 的方式
  7. asp.net2.0中通过CS代码来动态的指定aspx页面中head中的信息
  8. postgresql的系统列(system cloumns)
  9. ajax和spa的区别,在XX团上消费过一次不正规的Spa,现在过来两个月公安局打电话叫我过去...
  10. 如何动态创建asp.net 用户控件
  11. ios java 通信_Kurento Java Spring Client IOS – Web到IOS通信
  12. 前端系统化学习【JS篇】:(三)Javascript中的命名规范
  13. 读取网络抓包文件data得到参数
  14. mongodb启动很慢:[initandlisten] found 1 index(es) that
  15. python爬贴吧回复内容_Python爬虫_获取贴吧内容
  16. 高通平台抓取ramdump并用qcap解析
  17. cadvisor 容器安装部署
  18. python仓库管理
  19. 阿里巴巴Java面试题
  20. 计算机如何寻址,计算机运算之直接寻址法与间接寻址法

热门文章

  1. 微信小程序 云开发表数据一键清空
  2. Kaggle:Quora Question Pairs
  3. 幼儿园管理系统c语言,【资源学习】c语言程序代码,登录幼儿园200个小朋友的数据...
  4. mysql单列数据指定符号分割列(单行转单列)
  5. 《途客圈创业记:不疯魔,不成活》一一2.4 与iWeekend再续前缘
  6. 拳皇2000 全出招表 (ARC)
  7. 吃什么对眼睛好 你吃对了吗
  8. 【ELK】自定义ES-5.4.1插件步骤2——带action-amp;amp;gt;transportaction分层结构并包含详细的集成测试步骤
  9. android开发笔记之锁屏界面未读短信未接来电提醒(android 4.4)
  10. react-player一个很好用的直播组件,可以播放视频等等