C++内存管理和C语言内存管理差不多,可以看一下C语言内存管理
目录
二、C语言中动态内存管理方式:malloc/calloc/realloc/free
四、operator new与operator delete函数
一、C/C++内存分布
1.1 C/C++程序内存分配的几个区域
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数⽽分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
- 数据段(静态区):存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的⼆进制代码。
1.2 C/C++中程序内存区域划分:

二、C语言中动态内存管理方式:malloc/calloc/realloc/free
详细内容请看链接:
https://blog.youkuaiyun.com/qq_62473190/article/details/148482605?spm=1001.2014.3001.5501
三、C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1 new/delete操作内置类型
1.动态申请一个int类型的空间
int* ptr1 = new int;
2.动态申请一个int类型的空间并且初始化为10
int* ptr2 = new int(1000);3.动态申请10个int类型的空间
int* ptr3 = new int[10];4.动态申请10个int类型的空间并初始化为0
int* ptr4 = new int[10] {0};
5.动态申请10个int类型的空间并初始化为前五个,其余的初始化为0
int* ptr4 = new int[10] {1,2,3,4,5};
使用new申请空间,可以使用delete释放空间,如下所示:
delete ptr1;
delete ptr2;
delete[] ptr3;
delete[] ptr4;
delete[] ptr5;
注意1:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。
注意2:使用new来申请内置类型,虽然也能通过free来释放,但是不建议使用。尽量配对使用。
3.2 new/delete操作自定义类型
new/delete和malloc/free最大区别是new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数。
我们写一段代码来申请和释放自定义类型,代码如下所示:
#include<iostream> using namespace std; class A { public: A(int a1=1):_a1(a1) { cout << "A(int a1=1)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a1; }; int main() { A* p1 = new A(); delete p1; return 0; }我们使用new来申请自定义类型时,会自动调用A的构造函数,使用delete来释放空间会自动调用A的析构函数,运行结果如下所示:
注意,如果我们将A的构造函数的默认参数给去掉,再去运行这个代码,会出现错误,显示没有合适的构造函数可以用,如下所示:
#include<iostream> using namespace std; class A { public: A(int a1) :_a1(a1) { cout << "A(int a1=1)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a1; }; int main() { A* p1 = new A(); delete p1; return 0; }错误截图如下:
所以我们在使用new来申请自定义类型的时候需要传入一个参数,来调用A的构造函数。代码如下所示:
#include<iostream> using namespace std; class A { public: A(int a1) :_a1(a1) { cout << "A(int a1=1)" << endl; } ~A() { cout << "~A()" << endl; } private: int _a1; }; int main() { A* p1 = new A(1); delete p1; return 0; }
如果我们想要申请自定义类型的数组呢?以上面的A类类型为例:
A* p1=new A[3];
如果A类有默认构造函数,这样写是没有问题的,但是上面的类是没有默认构造函数的,那怎么办呢?
我们可以在申请内存的时候,创建三个A类型的对象,然后使用这三个对象初始化数组,如下所示:
int main() { A aa1(1); A aa2(1); A aa3(1); A* p1 = new A[3]{ aa1,aa2,aa3 }; delete[] p1; return 0; }但是这样写太麻烦了,我们申请自定义类型的数组,还要创建自定义类型的对象,然后再给数组初始化,有没有更好的方法呢?
方法一:使用匿名对象来初始化数组,代码如下:
int main() { A* p1 = new A[3]{ A(1),A(2),A(3) }; delete[] p1; return 0; }方法二:使用类型转化来初始化数组,代码如下:
int main() { A* p1 = new A[3]{ 1,2,3 }; delete[] p1; return 0; }大括号里面的1,2,3会通过隐示类型转化为A类型的对象。具体可以看类和对象(下)的类型转换:
https://blog.youkuaiyun.com/qq_62473190/article/details/151726758?spm=1001.2014.3001.5501
但是,如果类类型需要两个参数才能调用构造函数呢?那怎么使用隐示类型转化来初始化数组呢?代码如下:
#include<iostream> using namespace std; class B { public: B(int b1, int b2) :_b1(b1), _b2(b2) { cout << "B(int b1, int b2)" << endl; } ~B() { cout << "~B()" << endl; } private: int _b1; int _b2; }; int main() { B* p1 = new B[3]{ {1,2},{3,4},{5,6} }; delete[] p1; return 0; }
希望以上内容能够帮助读者在使用new的时候分清楚什么时候使用小括号,什么时候使用大括号,什么时候使用中括号。
delete的用法和内置类型的delete用法一样,使用delete在释放一个对象的时候就是delete p1;释放数组就是delete[] p1;尽量不要弄混。如果我们在释放数组的时候少了[],会只调用一次析构函数。有时候可能会出现错误。
下面举一个例子来说明一下少了方括号会怎么样。
我们来执行两个代码,
代码一:
#include<iostream> using namespace std; class A { public: A(int a1 = 1, int a2 = 2) :_a1(a1), _a2(a2) { } private: int _a1; int _a2; }; int main() { A* p1 = new A[10]; delete p1; }代码二:
#include<iostream> using namespace std; class B { public: B(int b1=1,int b2=2):_b1(b1),_b2(b2) { } ~B(){} private: int _b1; int _b2; }; int main() { B* p2 = new B[10]; delete p2; return 0; }以上两段代码都没有使用delete[]来释放数组空间,我们执行这两段代码,会发现代码一能够执行通过,代码二不能执行通过,这是为什么呢?按理来说两段代码应该都不能执行通过才对。会有很多人猜想是析构一次出错了,其实不是这个原因,代码一和代码二都只析构了一次。
代码二比代码一多了一个析构函数,如果有析构函数,在new的时候会在p2前面多开一个4字节的空间,用来存储对象个数,方便析构。如果在delete没有带[],就找不到前面多申请的4个字节,就只会释放动态内存开辟内存的一部分而导致出错。代码一没有析构函数,所以不会多申请4个字节,delete p1 就是从申请的位置开始释放。所以代码一能运行通过,代码二不能运行通过。如果我们都加上[]就没有这个问题了。
所以我们在释放数组的时候必须用delete[]。
四、operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
1.operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
2.operator delete:该函数最终是通过free来释放空间的
五、new和delete实现原理
5.1 内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申
请空间失败时会抛异常,malloc会返回NULL。
5.2 自定义类型
- new的原理
-
调用operator new函数申请空间
-
在申请的空间上执行构造函数,完成对象的构造
- delete原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
- new A[N]原理
-
调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象的空间的申请
-
在申请的空间上执行N次构造函数
- delete[] 原理
-
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
-
调用operator delete[]释放空间,实际在operator delete[]中调用operator 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在释放空间前会调用析构函数完成空间中资源的清理释放



被折叠的 条评论
为什么被折叠?



