C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为或堆(heap)。程序可以用堆来存储动态分配的对象,即那些在程序运行时创建的对象。动态对象的生存期由程序来控制 ,当动态对象不再使用时,程序必须显式的销毁它们。new操作符就是从自由存储区上为对象动态分配内存空间的。这里的自由存储区可以是堆,或者静态区。

1、new和delete的使用

C++中通过一对运算符new和delete来完成动态内存分配。new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象初始化;delete接受一个动态对象的指针,销毁对象,并释放对应内存。使用示例如下:

  1. void Test()
  2. {
  3. int *pi1 = new int;
  4. //pi1指向一个动态分配的4个字节(int型)、未初始化的无名对象;*pi1的值未定义
  5. int *pi2 = new int();
  6. //pi2指向的对象的值是2,动态分配4个字节( 1个 int) 的空间并初始化为2
  7. int *pi3 = new int[]; //动态分配12个字节( 3个 int) 的空间
  8. int *pi4 = new int(); //值初始化为0;*pi4为0
  9.  
  10. delete pi1;
  11. delete pi2;
  12. delete [] pi3;
  13. delete pi4;
  14. }

自由空间分配的内存是无名的,new无法为其分配的对象命名,而是返回一个指向该对象的指针。默认情况下,动态分配的对象是默认初始化的,即内置类型或组合类型的对象的值是未定义的,类类型的对象将用默认构造函数进行初始化。
    new和delete、 new[] 和delete[] 一定匹配使用 , 一定匹配使用 , 一定匹配使用 ! ! !
重要的事说三遍! 否则可能出现内存泄露甚至崩溃的问题。
2、深入探究new和delete、 new[] 和delete[]内部实现机制

通过下面这段代码我们来详细比较一下new和delete、 new[] 和delete[]内部实现

  1. class Array
  2. {
  3. public :
  4. Array(size_t size = )//构造函数
  5. : _size(size)
  6. , _a()
  7. {
  8. cout << "Array(size_t size) " << endl;
  9. if (_size > )
  10. {
  11. _a = new int[size];
  12. }
  13. }
  14. ~Array() //析构函数
  15. {
  16. cout << "~Array() " << endl;
  17. if (_a)
  18. {
  19. delete[] _a;
  20. _a = ;
  21. _size = ;
  22. }
  23. }
  24. private:
  25. int*_a;
  26. size_t _size;
  27. };
  28. void Test()
  29. {
  30. Array* p1 = (Array*)malloc(sizeof(Array));
  31. Array* p2 = new Array;
  32. Array* p3 = new Array();
  33. Array* p4 = new Array[];
  34. free(p1);
  35. delete p2;
  36. delete p3;
  37. delete[] p4;
  38. }
  39. int main()
  40. {
  41. Test();
  42. getchar();
  43. return ;
  44. }

转到反汇编可以看到,在call指令处调用了operator new:

转到定义处可以看到operator new 的具体原型:

其实在operator new的底层同样调用了malloc分配空间,它先为对象分配所申请的内存空间,然后底层调用构造函数构造对象

再按F10程序来到了构造函数

执行完之后,输出

此时new已经完成了申请空间的任务,且调用构造函数创建了对象。同样,detele的定义如下

而delete是先调用析构函数清除对象。然后调用operator detele释放空间。

按F10跳转到了析构函数,析构之后:

然后空间才被释放:

new []与delete []内部执行过程相同,只是底部调用的是operator new []和operator delete []

Array* p4 = new Array[10];
delete[] p4;

执行这两条语句的时候实际上调用operator new[](10*sizeof(Array)+4)分配大小为10*sizeof(Array)+4空间,其中多的四个字节空间用于存放N(10)这个数字以便于delete中调用析构函数析构对象(调用析构函数的次数),空间申请好了之后调用构造函数创建对象。delete[] p4执行的时候首先取N(10)对象个数,然后调用析构函数析构对象,最后用operator delete[]函数释放空间。

3、关键字之间的匹配使用问题

  1. void Test ()
  2. {
  3. // 以下代码没有匹配使用, 会发生什么? 有内 存泄露吗? 会崩 溃吗?
  4. int* p4 = new int;
  5. int* p5 = new int() ;
  6. int* p6 = new int[] ;
  7. int* p7 = (int*) malloc(sizeof (int) ) ;
  8. delete[] p4 ;
  9. delete p5 ;
  10. free(p5 ) ;
  11. delete p6 ;
  12. delete p7 ;
  13. }

运行结果:没有崩溃。但是当把int换成自定义类型之后则会出现问题。因为内置类型一般不会调用构造函数和析构函数,而自定义类型会,所以是析构对象的时候出现内存泄漏,导致程序崩溃。虽然弄清了问题,但还是建议任何类型都要匹配使用。

4、定位new表达式

new表达式,默认下把内存开辟到堆区。使用定位new表达式,可以在指定地址区域(栈区、堆区、静态区)构造对象,这好比是把内存开辟到指定区域。

定位new表达式调用 void *operator new(size_t, void *); 分配内存。其常见形式有:

  1. new(address) type;
  2. new(address) type(initializers);
  3. new(address) type[size];
  4. new(address) type[size]{braced initializer list};

address必须是个指针,指向已经分配好的内存。

示例代码:

  1. #include <iostream>
  2. using namespace std;
  3. char addr1[]; //把内存分配到全局/静态区
  4. int main()
  5. {
  6. char addr2[]; //把内存分配到栈区
  7. char *addr3 = new char[]; //把内存分配到堆区
  8. cout << "addr1 = " << (void*)addr1 << endl;
  9. cout << "addr2 = " << (void*)addr2 << endl;
  10. cout << "addr3 = " << (void*)addr3 << endl;
  11. int *p = nullptr;
  12. //把对象构造到静态区
  13. p = new(addr1)int;
  14. *p = ;
  15. cout << (void*)p << " " << *p << endl;
  16. //把对象构造到栈区
  17. p = new(addr2)int;
  18. *p = ;
  19. cout << (void*)p << " " << *p << endl;
  20. //把内存分配到堆区
  21. p = new(addr3)int;
  22. *p = ;
  23. cout << (void*)p << " " << *p << endl;
  24. cin.get();
  25. return ;
  26. }

程序中,首先使用变量或new为对象分配空间,然后通过定位new表达式,完成构造函数的调用,将对象创建在已经被分配好的内存中。

定位new表达式不能调用delete删除 placement new的对象,需要人为的调用对象的析构函数,并且人为的释放掉占用的内存。

  1. #include <iostream>
  2. #include <new>
  3.  
  4. using namespace std;
  5.  
  6. const int chunk = ;
  7.  
  8. class Foo
  9. {
  10. public:
  11. int val(){return _val;}
  12. Foo(){_val=;}
  13. private:
  14. int _val;
  15. };
  16.  
  17. int main()
  18. {
  19. // 预分配内存buf
  20. char *buf = new char[sizeof(Foo) * chunk];
  21.  
  22. // 在buf中创建一个Foo对象
  23. Foo *pb=new (buf) Foo;
  24. // 检查一个对象是否被放在buf中
  25. if(pb->val()==) cout<<"new expression worked!"<<endl;
  26. // 这里不存在与定位new表达式匹配的delete表达式,即:delete pb, 其实只是为了
  27. // 释放内存的话,我们不需要这样的表达式,因为定位new表达式并不分配内存。
  28. // 如果在析构函数中要做一些其他的操作呢?就要显示的调用析构函数。
  29. // 当程序不再需要buf时,buf指向的内存被删除,它所包含的任何对象的生命期也就
  30. // 都结束了。
  31.  
  32. delete[] buf;
  33. return ;
  34. }

一句话:定位new表达式用于在已分配的原始空间中调用构造函数初始化一个对象。

5、模拟实现new和delete

  1. class Test
  2. {};
  3.  
  4. Test* newdelete( )
  5. {
  6. Test* p1 = NULL;
  7. //1、分配空间 2.利用new的定位表达式显式调用构造函数
  8. if (p1 = (Test*)malloc(sizeof(Test)))
  9. return p1;
  10. else
  11. throw bad_alloc(); //内存分配失败时抛异常
  12. new(p1)Test; //NEW(P1+I)Test(参数列表);
  13.  
  14. //3、析构函数 4、释放空间
  15. p1->~Test();
  16. free(p1);
  17. }
  18.  
  19. Test* newdalete_(size_t N)
  20. {
  21. Test* p2 = NULL;
  22. //1、分配空间 2.显示调用构造函数
  23. if (p2 = (Test*)malloc(sizeof(Test)*N + ))
  24. return p2;
  25. else
  26. throw bad_alloc(); //内存分配失败时抛异常
  27. *((int*)p2) = N;
  28. p2 = (Test*)((int*)p2 + );
  29. for (int i = ; i < N; ++i)
  30. {
  31. new(p2 + i)Test;
  32. }
  33.  
  34. int n = *((int*)p2 - );
  35. //3、析构函数 4、释放空间
  36. for (int i = ; i < n; ++i)
  37. {
  38. p2[i].~Test();
  39. //(p1 + 1)->~AA(); //也可以
  40. }
  41. free((int*)p2 - );
  42. }

总结:
1. operator new/operator delete operator new[] /operator delete[] 和 malloc/free用法一
样。
2. 他们只负责分配空间/释放空间, 不会调用对象构造函数/析构函数来初始化/清理对象。
3. 实际operator new和operator delete只是malloc和free的一层封装。

【 new作用】
调用operator new分配空间。
调用构造函数初始化对象。

【 delete作用】
调用析构函数清理对象
调用operator delete释放空间

【 new[] 作用】
调用operator new分配空间。
调用N次构造函数分别初始化每个对象。

【 delete[] 作用】
调用N次析构函数清理对象。  调用operator delete释放空间。

 

new/new[]和delete/delete[]是如何分配空间以及释放空间的的更多相关文章

  1. 为什么delete后磁盘空间没有释放而truncate会释放?

    背景 因项目需求,需要清理一批旧数据,腾出空间给新数据,让同事负责这件事.料想会很顺利,但很快找到我,并告知在postgresql中把一张大的数据表删除掉了,查询表的size并没有改变. 我震惊了,问 ...

  2. 【Windows核心编程】重载类成员函数new / new[] / delete / delete[]

    // Heap.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <Windows.h> #include &l ...

  3. c++ 用new后delete,而继续输出指针后果 new/new[]/delete/delete[]区别

    #include<iostream> #include<cstring> #include <string.h> int main(){ ]; ;i<;i++ ...

  4. C++ 动态分配 和 内存分配和内存释放

    动态分配 动态分配可以说是指针的关键所在.不需要通过定义变量,就可以将指针指向分配的内存.也许这个概念看起来比较模糊,但是确实比较简单.下面的代码示范如何为一个整数分配内存: int *pNumber ...

  5. oracle创建用户、表空间、临时表空间、分配权限步骤详解

    首先登陆管理员账号,或者有DBA权限的用户,接下来依次: --查询所有用户select * from dba_users;--创建新用户create user gpmgt identified by ...

  6. experiment : 在私有堆和默认进程堆中, 测试能分配的堆空间总和, 每次能分配的最大堆空间

    实验环境: Win7X64Sp1 + vs2008,  物理内存16GB. 实验结论: *  进程堆的最大Size并没有使用完剩余的物理内存    *  每次能分配的最大堆空间接近2M, 不管是私有堆 ...

  7. JVM 调优测试 之 故意分配小的堆空间,观察gc回收打印的内容

    测试代码如下: @Test public void testPrintGcDetail(){ HashMap<String, List> gcMap = new HashMap<&g ...

  8. [008]new、delete及动态内存分配

    1.new和delete都会用,这里只声明一点: C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针. 比如下面的代码: #include<iostream> using ...

  9. 动态内存分配(new)和释放(delete)

    在之前我们所写过的程序中,所必需的内存空间的大小都是在程序执行之前就已经确定了.但如果我们需要内存大小为一个变量,其数值只有在程序运行时 (runtime)才能确定,例如有些情况下我们需要根据用户输入 ...

随机推荐

  1. 调试 - Visual Studio调试

    Visual Studio - 调试 异常处理机制 windows预定义了一系列的异常错误码,每种程序异常都有一个对应的错误码,windows系统将这些类似键值对关系的数据存储在异常处理表中(称为SE ...

  2. html超文本标记语言基础一

    1,基本格式 <!DOCTYPE html> //声明为 HTML5 文档 <html> <head> <meta charset="utf-8&q ...

  3. JavaWeb - 目录

    参考:https://www.cnblogs.com/xdp-gacl/tag/JavaWeb%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/default.html?pag ...

  4. PCM EQ DRC 音频处理

    PCM Pulse-code modulation的缩写,中文译名是脉冲编码调制.(I2S仅仅是PCM的一个分支,接口定义都是一样的, I2S的采样频率一般为44.1KHZ和48KHZ做,PCM采样频 ...

  5. tomcat apr 部署

    背景 这还是为了高并发的事,网上说的天花乱坠的,加了apr怎么怎么好,我加了,扯淡.就是吹牛用.我还是认为,性能问题要考设计逻辑和代码解决,这些都是锦上添花的. 步骤 1 windows 部署简单,虽 ...

  6. MinGW GCC 7.3.0 2018年1月25日 出炉啦

    GCC_7.3.0._for_MSYS2.7z for x86 x64 63.68 MB发布日期: 2018-01-26 下载地址: https://forum.videohelp.com/attac ...

  7. vue与jquery合作

    2017年2月26日 14:59:34 星期日 场景: jquery的$.post, $.get是$.ajax的封装, 是异步的 因此, 有肯能在初始化vue实例的时候, 异步请求的结果还没返回, 这 ...

  8. 关于EditText一些效果

    效果如图,由TextView  View(竖线) EditText与ImageView组成 首先更改draw able中shape代码 <?xml version="1.0" ...

  9. python学习第11天 迭代器

    函数的名称 闭包 迭代器 递归

  10. [swoole]swoole常见问题总汇

    1.在daemon模式下Task异步任务写入文件需要采用绝对路径: 1.Task异步任务中操作数据库,如果仅仅只是在启动程序之初进行一次数据库链接,链接会在一定的时间后自动断开,应对这样的情况的最好办 ...