new/new[]和delete/delete[]是如何分配空间以及释放空间的
C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为或堆(heap)。程序可以用堆来存储动态分配的对象,即那些在程序运行时创建的对象。动态对象的生存期由程序来控制 ,当动态对象不再使用时,程序必须显式的销毁它们。new操作符就是从自由存储区上为对象动态分配内存空间的。这里的自由存储区可以是堆,或者静态区。
1、new和delete的使用
C++中通过一对运算符new和delete来完成动态内存分配。new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象初始化;delete接受一个动态对象的指针,销毁对象,并释放对应内存。使用示例如下:
- void Test()
- {
- int *pi1 = new int;
- //pi1指向一个动态分配的4个字节(int型)、未初始化的无名对象;*pi1的值未定义
- int *pi2 = new int();
- //pi2指向的对象的值是2,动态分配4个字节( 1个 int) 的空间并初始化为2
- int *pi3 = new int[]; //动态分配12个字节( 3个 int) 的空间
- int *pi4 = new int(); //值初始化为0;*pi4为0
- delete pi1;
- delete pi2;
- delete [] pi3;
- delete pi4;
- }
自由空间分配的内存是无名的,new无法为其分配的对象命名,而是返回一个指向该对象的指针。默认情况下,动态分配的对象是默认初始化的,即内置类型或组合类型的对象的值是未定义的,类类型的对象将用默认构造函数进行初始化。
new和delete、 new[] 和delete[] 一定匹配使用 , 一定匹配使用 , 一定匹配使用 ! ! !
重要的事说三遍! 否则可能出现内存泄露甚至崩溃的问题。
2、深入探究new和delete、 new[] 和delete[]内部实现机制
通过下面这段代码我们来详细比较一下new和delete、 new[] 和delete[]内部实现
- class Array
- {
- public :
- Array(size_t size = )//构造函数
- : _size(size)
- , _a()
- {
- cout << "Array(size_t size) " << endl;
- if (_size > )
- {
- _a = new int[size];
- }
- }
- ~Array() //析构函数
- {
- cout << "~Array() " << endl;
- if (_a)
- {
- delete[] _a;
- _a = ;
- _size = ;
- }
- }
- private:
- int*_a;
- size_t _size;
- };
- void Test()
- {
- Array* p1 = (Array*)malloc(sizeof(Array));
- Array* p2 = new Array;
- Array* p3 = new Array();
- Array* p4 = new Array[];
- free(p1);
- delete p2;
- delete p3;
- delete[] p4;
- }
- int main()
- {
- Test();
- getchar();
- return ;
- }
转到反汇编可以看到,在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、关键字之间的匹配使用问题
- void Test ()
- {
- // 以下代码没有匹配使用, 会发生什么? 有内 存泄露吗? 会崩 溃吗?
- int* p4 = new int;
- int* p5 = new int() ;
- int* p6 = new int[] ;
- int* p7 = (int*) malloc(sizeof (int) ) ;
- delete[] p4 ;
- delete p5 ;
- free(p5 ) ;
- delete p6 ;
- delete p7 ;
- }
运行结果:没有崩溃。但是当把int换成自定义类型之后则会出现问题。因为内置类型一般不会调用构造函数和析构函数,而自定义类型会,所以是析构对象的时候出现内存泄漏,导致程序崩溃。虽然弄清了问题,但还是建议任何类型都要匹配使用。
4、定位new表达式
new表达式,默认下把内存开辟到堆区。使用定位new表达式,可以在指定地址区域(栈区、堆区、静态区)构造对象,这好比是把内存开辟到指定区域。
定位new表达式调用 void *operator new(size_t, void *); 分配内存。其常见形式有:
- new(address) type;
- new(address) type(initializers);
- new(address) type[size];
- new(address) type[size]{braced initializer list};
address必须是个指针,指向已经分配好的内存。
示例代码:
- #include <iostream>
- using namespace std;
- char addr1[]; //把内存分配到全局/静态区
- int main()
- {
- char addr2[]; //把内存分配到栈区
- char *addr3 = new char[]; //把内存分配到堆区
- cout << "addr1 = " << (void*)addr1 << endl;
- cout << "addr2 = " << (void*)addr2 << endl;
- cout << "addr3 = " << (void*)addr3 << endl;
- int *p = nullptr;
- //把对象构造到静态区
- p = new(addr1)int;
- *p = ;
- cout << (void*)p << " " << *p << endl;
- //把对象构造到栈区
- p = new(addr2)int;
- *p = ;
- cout << (void*)p << " " << *p << endl;
- //把内存分配到堆区
- p = new(addr3)int;
- *p = ;
- cout << (void*)p << " " << *p << endl;
- cin.get();
- return ;
- }
程序中,首先使用变量或new为对象分配空间,然后通过定位new表达式,完成构造函数的调用,将对象创建在已经被分配好的内存中。
定位new表达式不能调用delete删除 placement new的对象,需要人为的调用对象的析构函数,并且人为的释放掉占用的内存。
- #include <iostream>
- #include <new>
- using namespace std;
- const int chunk = ;
- class Foo
- {
- public:
- int val(){return _val;}
- Foo(){_val=;}
- private:
- int _val;
- };
- int main()
- {
- // 预分配内存buf
- char *buf = new char[sizeof(Foo) * chunk];
- // 在buf中创建一个Foo对象
- Foo *pb=new (buf) Foo;
- // 检查一个对象是否被放在buf中
- if(pb->val()==) cout<<"new expression worked!"<<endl;
- // 这里不存在与定位new表达式匹配的delete表达式,即:delete pb, 其实只是为了
- // 释放内存的话,我们不需要这样的表达式,因为定位new表达式并不分配内存。
- // 如果在析构函数中要做一些其他的操作呢?就要显示的调用析构函数。
- // 当程序不再需要buf时,buf指向的内存被删除,它所包含的任何对象的生命期也就
- // 都结束了。
- delete[] buf;
- return ;
- }
一句话:定位new表达式用于在已分配的原始空间中调用构造函数初始化一个对象。
5、模拟实现new和delete
- class Test
- {};
- Test* newdelete( )
- {
- Test* p1 = NULL;
- //1、分配空间 2.利用new的定位表达式显式调用构造函数
- if (p1 = (Test*)malloc(sizeof(Test)))
- return p1;
- else
- throw bad_alloc(); //内存分配失败时抛异常
- new(p1)Test; //NEW(P1+I)Test(参数列表);
- //3、析构函数 4、释放空间
- p1->~Test();
- free(p1);
- }
- Test* newdalete_(size_t N)
- {
- Test* p2 = NULL;
- //1、分配空间 2.显示调用构造函数
- if (p2 = (Test*)malloc(sizeof(Test)*N + ))
- return p2;
- else
- throw bad_alloc(); //内存分配失败时抛异常
- *((int*)p2) = N;
- p2 = (Test*)((int*)p2 + );
- for (int i = ; i < N; ++i)
- {
- new(p2 + i)Test;
- }
- int n = *((int*)p2 - );
- //3、析构函数 4、释放空间
- for (int i = ; i < n; ++i)
- {
- p2[i].~Test();
- //(p1 + 1)->~AA(); //也可以
- }
- free((int*)p2 - );
- }
总结:
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[]是如何分配空间以及释放空间的的更多相关文章
- 为什么delete后磁盘空间没有释放而truncate会释放?
背景 因项目需求,需要清理一批旧数据,腾出空间给新数据,让同事负责这件事.料想会很顺利,但很快找到我,并告知在postgresql中把一张大的数据表删除掉了,查询表的size并没有改变. 我震惊了,问 ...
- 【Windows核心编程】重载类成员函数new / new[] / delete / delete[]
// Heap.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <Windows.h> #include &l ...
- c++ 用new后delete,而继续输出指针后果 new/new[]/delete/delete[]区别
#include<iostream> #include<cstring> #include <string.h> int main(){ ]; ;i<;i++ ...
- C++ 动态分配 和 内存分配和内存释放
动态分配 动态分配可以说是指针的关键所在.不需要通过定义变量,就可以将指针指向分配的内存.也许这个概念看起来比较模糊,但是确实比较简单.下面的代码示范如何为一个整数分配内存: int *pNumber ...
- oracle创建用户、表空间、临时表空间、分配权限步骤详解
首先登陆管理员账号,或者有DBA权限的用户,接下来依次: --查询所有用户select * from dba_users;--创建新用户create user gpmgt identified by ...
- experiment : 在私有堆和默认进程堆中, 测试能分配的堆空间总和, 每次能分配的最大堆空间
实验环境: Win7X64Sp1 + vs2008, 物理内存16GB. 实验结论: * 进程堆的最大Size并没有使用完剩余的物理内存 * 每次能分配的最大堆空间接近2M, 不管是私有堆 ...
- JVM 调优测试 之 故意分配小的堆空间,观察gc回收打印的内容
测试代码如下: @Test public void testPrintGcDetail(){ HashMap<String, List> gcMap = new HashMap<&g ...
- [008]new、delete及动态内存分配
1.new和delete都会用,这里只声明一点: C++ 没有明确定义如何释放指向不是用 new 分配的内存地址的指针. 比如下面的代码: #include<iostream> using ...
- 动态内存分配(new)和释放(delete)
在之前我们所写过的程序中,所必需的内存空间的大小都是在程序执行之前就已经确定了.但如果我们需要内存大小为一个变量,其数值只有在程序运行时 (runtime)才能确定,例如有些情况下我们需要根据用户输入 ...
随机推荐
- 调试 - Visual Studio调试
Visual Studio - 调试 异常处理机制 windows预定义了一系列的异常错误码,每种程序异常都有一个对应的错误码,windows系统将这些类似键值对关系的数据存储在异常处理表中(称为SE ...
- html超文本标记语言基础一
1,基本格式 <!DOCTYPE html> //声明为 HTML5 文档 <html> <head> <meta charset="utf-8&q ...
- JavaWeb - 目录
参考:https://www.cnblogs.com/xdp-gacl/tag/JavaWeb%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93/default.html?pag ...
- PCM EQ DRC 音频处理
PCM Pulse-code modulation的缩写,中文译名是脉冲编码调制.(I2S仅仅是PCM的一个分支,接口定义都是一样的, I2S的采样频率一般为44.1KHZ和48KHZ做,PCM采样频 ...
- tomcat apr 部署
背景 这还是为了高并发的事,网上说的天花乱坠的,加了apr怎么怎么好,我加了,扯淡.就是吹牛用.我还是认为,性能问题要考设计逻辑和代码解决,这些都是锦上添花的. 步骤 1 windows 部署简单,虽 ...
- 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 ...
- vue与jquery合作
2017年2月26日 14:59:34 星期日 场景: jquery的$.post, $.get是$.ajax的封装, 是异步的 因此, 有肯能在初始化vue实例的时候, 异步请求的结果还没返回, 这 ...
- 关于EditText一些效果
效果如图,由TextView View(竖线) EditText与ImageView组成 首先更改draw able中shape代码 <?xml version="1.0" ...
- python学习第11天 迭代器
函数的名称 闭包 迭代器 递归
- [swoole]swoole常见问题总汇
1.在daemon模式下Task异步任务写入文件需要采用绝对路径: 1.Task异步任务中操作数据库,如果仅仅只是在启动程序之初进行一次数据库链接,链接会在一定的时间后自动断开,应对这样的情况的最好办 ...