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释放空间。

 

C++—动态内存管理之深入探究new和delete的更多相关文章

  1. C++动态内存管理之深入探究new和delete

    C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为自由空间(free store)或堆(heap).程序用堆来存储动态分配的对象,即,那些程序运行时分配的对象.动态对象 ...

  2. C++动态内存管理之shared_ptr、unique_ptr

    C++中的动态内存管理是通过new和delete两个操作符来完成的.new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针.delete调用时,销毁对象,并释放对象所在的内存 ...

  3. uCGUI动态内存管理

    动态内存的堆区 /* 堆区共用体定义 */ typedef union { /* 可以以4字节来访问堆区,也可以以1个字节来访问 */ ]; /* required for proper aligne ...

  4. Keil C动态内存管理机制分析及改进(转)

    源:Keil C动态内存管理机制分析及改进 Keil C是常用的嵌入式系统编程工具,它通过init_mempool.mallloe.free等函数,提供了动态存储管理等功能.本文通过对init_mem ...

  5. (原创)动态内存管理练习 C++ std::vector<int> 模拟实现

    今天看了primer C++的 “动态内存管理类”章节,里面的例子是模拟实现std::vector<std::string>的功能. 照抄之后发现编译不通过,有个库函数调用错误,就参考着自 ...

  6. FreeRTOS 动态内存管理

    以下转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面 ...

  7. C++程序设计入门 引用和动态内存管理学习

    引用: 引用就是另一个变量的别名,通过引用所做的读写操作实际上是作用于原变量上. 由于引用是绑定在一个对象上的,所以定义引用的时候必须初始化. 函数参数:引用传递 1.引用可做函数参数,但调用时只需 ...

  8. 动态内存管理详解:malloc/free/new/delete/brk/mmap

    c++ 内存获取和释放 new/delete,new[]/delete[] c 内存获取和释放 malloc/free, calloc/realloc 上述8个函数/操作符是c/c++语言里常用来做动 ...

  9. oracle结构-内存结构与动态内存管理

    内存结构与动态内存管理 内存是影响数据库性能的重要因素. oracle8i使用静态内存管理,即,SGA内是预先在参数中配置好的,数据库启动时就按这些配置来进行内在分配,oracle10g引入了动态内存 ...

随机推荐

  1. iOS ReactiveCocoa(RAC)学习详解

    概述: ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的一个框架,有时间,自己也了解学习了一下这个框架的一些基本的应用,其实你要学的话网上是有很多很多的博 ...

  2. asp.net core mvc实现伪静态功能

    在大型网站系统中,为了提高系统访问性能,往往会把一些不经常变得内容发布成静态页,比如商城的产品详情页,新闻详情页,这些信息一旦发布后,变化的频率不会很高,如果还采用动态输出的方式进行处理的话,肯定会给 ...

  3. 游戏UI框架设计(一) : 架构设计理论篇

    游戏UI框架设计(一) ---架构设计理论篇 前几天(2017年2月)看到一篇文章,国内王健林.马云等大咖们看好的未来十大最有"钱途"产业中,排名第一的就是"泛娱乐&qu ...

  4. Java基于文件的对象存储

    工作中经常需要处理对象的处理,有的时候还需要将对象保存到文件中做持久化. 特别是当不能使用数据库的时候就特别需要一个简单的对象集合的增删改查操作, 于是就有了下面这个文件DB的工具类 package ...

  5. c++ 调用dl里的导出类

    来源:http://blog.csdn.net/yysdsyl/article/details/2626033 动态dll的类导出:CPPDll2->test.h #pragma once // ...

  6. ASP.NET通用权限框架 权限管理系统源码jquery 精美UI源码

    软件技术开发,合作请联系QQ:858-048-581 开发工具 VS2010 .sql2005.2008等(在Sql server数据执行脚本即可)  VS2010 打开保证本地运行成功(数据库.源代 ...

  7. 面向对象重写(override)与重载(overload)区别 (转)

    一.重写(override) override是重写(覆盖)了一个方法,以实现不同的功能.一般是用于子类在继承父类时,重写(重新实现)父类中的方法. 重写(覆盖)的规则: 1.重写方法的参数列表必须完 ...

  8. Node.js~在linux上的部署~外网不能访问node.js网站的解决方法

    这是上一篇node.js部署到linux上的后续文章,当我们安装完node.js之后,建立了sailsjs的网站,然后在外面电脑上无法访问这个网站,这个问题我们如何去解决? 解决思路: 查看linux ...

  9. 第25篇 jQuer快速学习(上)---选择器和DOM操作

    这个文章经历的时间比较长,不是因为jQuery比较难,而是东西比较多,真心是个体力活.所以本来想把jQuery做成一篇去写,但由于写的时候发现jQuery发现写成一篇的话过于长,对于阅读起来也不是一个 ...

  10. 使用咪咕云做C站视频直链源

    首先我们先百度搜索一下“咪咕云” 点击进入-->用户注册或登录 注册时选择个人用户-->前往邮箱激活-->进入邮箱激活成功后重新登录 登录后在控制台选择“云点播” 即可进行上传视频了 ...