C++中变量的内存分布和C语言是一样的。
这篇文章主要是C++专属开辟和释放空间的方式new和delete
文章目录
- 一、内存分布
- 二、C++的动态内存管理方式
- 2.1.new和delete开辟内置类型和非自定义变量
- 三、new和delete底层
- 3.1.重载专属的operator new和operator delete
- 四、定位new表达式(placement-new)
- 五、malloc/free和new/delete的区别
- 六、内存泄漏
一、内存分布
程序的本质就是为了管理数据和对数据进行处理,为了更好的保存和管理数据,操作系统对内存进行了划分,不同的数据存放的内存区域不同:
栈区向下增长、堆区向上增长,这也就说明栈区开辟的空间是先开辟在高地址,后开辟的空间在低地址,堆区开辟空间先开辟在低地址,后开辟的空间在高地址。当然也会有先开辟的空间被释放,后开辟的空间占用先开辟的空间,就不符合这种情况了。
注意;const变量虽然有常属性,但是不是定义在常量区的,一般根据const变量的类型放在栈(局部变量)或者静态区(static修饰)。
二、C++的动态内存管理方式
C语言中有malloc、realloc、calloc和free的动态内存管理方式,由于C++兼容C语言,因此C语言的这些方式可以在C++中继续使用。但为了方便,C++有自己的动态内存管理方式。
即通过new和delete操作符进行动态内存管理。
2.1.new和delete开辟内置类型和非自定义变量
其语法为:
void Test()
{
// 动态申请一个int类型的空间
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为1
int* ptr2 = new int(1);
// 动态申请10个int类型的空间
int* ptr3 = new int[3];
// 动态申请3个int类型的空间并初始化
int* ptr4 = new int[3]{1, 2, 3};//如果
//释放掉开辟的空间 delete加变量名即可,如果是数组要在变量名之前加[]
delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;
}
对于内置类型,new 和free与malloc和free没有区别。
#include<iostream>
using namespace std;
class Test
{
public:
Test()
{
std::cout << "调用构造函数" << std::endl;
}
~Test()
{
std::cout << "调用析构函数" << std::endl;
}
private:
int _test;
};
void Test()
{
//申请一个Test类型的对象
Test* p1 = new Test;
delete p1;
cout << endl;
//申请10个Test类型的对象
Test* p2 = new Test[10];
delete[] p2;
cout << endl;
}
int main()
{
Test T;
cout << endl;
Test();
return 0;
}
对于自定义类型,在new时会自动调用其构造函数初始化,delete时会调用其析构函数,而malloc和free仅仅只是开空间。
注意delete处理数组类类型的时候,会对每一个数组对象都调用它们的析构函数,然后再释放它们所占用的内存空间,所以如果不加[],内存会不匹配导致程序崩溃。不过加不加[]对内置类型没有区别,因为内置类型只需要释放空间。所以还是建议加上[]
三、new和delete底层
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数(不是函数重载),new在底层调用operator new全局函数来申请空间,并且调用类的构造函数,delete在底层通过operator delete全局函数来释放空间,并调用类的析构函数。
所以,operator new与malloc开辟空间失败后的处理方式不桶,malloc失败返回NULL,operator new失败以后抛异常。
抛异常会捕获出现错误的语句从而直接跳转到catch语句中,从而不会执行错误语句后面的语句。如果没有出错则会继续执行,catch不会执行。
综上,new和delete在底层的原理为:
-
对于内置类型开辟空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
-
对于自定义类型,new单个对象时会调用operator new函数申请空间并在申请的空间上执行构造函数,完成对象的构造。
-
delete单个对象时在空间上执行析构函数,完成对象中资源的清理工作并 调用operator delete函数释放对象的空间。
-
对于一个内置类型的数组,new T[N]调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请,并在申请的空间上执行N次构造函数。
-
delete[],在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理,并且调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
3.1.重载专属的operator new和operator delete
通过写了类专属重载就不用调用全局的operator new与operator delete了。
使用这种方式,实现使用内存池申请和释放内存,提高效率。
四、定位new表达式(placement-new)
对于一个已经开辟好的内存空间,可以显示地调用类的构造函数,从而给这块内存初始化一个对象。
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
这种写法等价于Test* p = new Test;
。
五、malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[ ]中指定对象个数即可。
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
六、内存泄漏
内存泄漏通常是指由于程序员自己的疏忽和程序设计的错误等原因导致未能释放已经不再使用的内存的情况。
内存泄漏是指向这块空间的指针丢失了,导致我们无法控制这块内存。但是这块并没有还给编译器,依然被占用。
所以malloc、new申请空间的本质是将一块内存的使用权交给使用者,free、delete释放内存空间的本质是交还使用权给系统,系统就可以再将这块空间分配给别人。
一般的程序内存泄漏危害并不是很大,因为程序进程结束以后就会被释放掉。
但是对于长期运行的程序如服务器后台程序危害就非常大。出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏的几种情况:
- 堆内存泄漏(Heap leak)
通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。 - 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
避免内存泄漏的方式:
- 良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。如果碰上异常时,就算注意释放了,还是可能会出问题,所以需要智能指针来管理。
- 采用RAII思想或者智能指针来管理资源。
- 公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。