【C++入门】C/C++内存管理 --建议收藏
C/C++内存管理
- 前言
- 一、内置类型
- 二、自定义类型
- 三、operator new/operator delete
- 四、new和delete的总结
- 小测试,画出下面的st与pst所在栈帧位置
- 五、定位new表达式
- 六、malloc/free和new/delete的区别
- 总结
前言
本篇主要比较从malloc,free的组合到new,delete的变化
,本章会简述其过程为什么是这样的!!
一、内置类型
#include<iostream>
using namespace std;
int main()
{
//C当中开辟十个整形空间
int* pi = (int*)malloc(sizeof(int) * 10);
free(pi);
pi = NULL;
//C++当中开辟十个整形空间
int* pi2 = new int[10];
delete pi2;
pi2 = nullptr;
return 0;
}
可以发现对于内置类型来说,我们现在能观察到的就是new相对于malloc可以少写一个返回值,并且new是一个操作符。但是大体上我们观察malloc和new是差不多的,看完后面再总结这点。
二、自定义类型
以栈为例,展开new对于自定义类型不同于malloc的不同之处。
class Stack
{
public:Stack(int x = 10)
:a(new int[x])
,_capacity(x)
,_size(0)
{
cout << "Stack(int x = 10)" << endl;
}
private:
int _size;
int _capacity;
int* a;
};
int main()
{//C
Stack* pst = (Stack*)malloc(sizeof(Stack));
free(pst);
pst = NULL;
//C++
Stack* pst2 = new Stack;
delete(pst2);
pst2 = nullptr;
return 0;
}
- 结论1:
可以从调试当中看出,我们的new对于内置类型会调用对象的构造函数进行初始化,并且还要说一点,他比起malloc还会对异常进行处理,当我们尝试在32位的情况开辟2g的内存的时候,我们看看编译器会报什么错–抛异常
- 抛异常后的选择
1.抛异常了,所以一般我们在new完之后要记得捕捉异常,因为有的时候有些地方申请内存失败,但是他罪不至死,通过捕捉异常我们可以进行再处理!
2.不做任何处理
- 异常捕捉打印结果
bad allocation,就是我们捕捉异常打印的结果,从下面的代码也可以看见我们的“haha’”并没有打印,说明一出现异常的时候catch就把我们的异常捕获了。
int main()
{
size_t i = 2;
try
{
char* pi = new char[i * 1024 * 1024 * 1024];
cout << "haha" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
三、operator new/operator delete
这个函数不是重载函数,不是重载函数,不是重载函数!!!
new和delete是用户在动态申请内存和释放所用到的操作符,operator new和operator delete是系统提供的全局函数,全局函数,全局函数!!!
废话不多说,我们直接先来看看底层代码实现
/* operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败, 尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。 */
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) {
// try to allocate size bytes
void *p;
// ==0即申请失败
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;//定义一个异常对象
_RAISE(nomem);//对异常做处理
}
return (p);
}
我们可以看出,operator new对比起malloc只是多了一个异常的处理,内部动态申请内存空间也是由malloc代劳,它的返回值和malloc甚至都是一模一样,所以调用operator new和malloc并太大区别,只是在抛异常这点做了处理
,并且可以看出他没有调用构造函数初始化!
operator delete底层涉及到阻塞队列,线程等概念,在这里我们初步理解为operator delete只是对free进行了一个封装,用起来和free差不多即可。当中释放空间的函数是用free的。
总结:假设对A类来说,new作用符就会干两件事情,调用operator new,和调用A的构造函数进行初始化。
四、new和delete的总结
1.对于内置类型来说,new和malloc,free和delete基本类似,不同的地方在于,new/delete是申请/释放单个元素的空间,new[]和delete[]申请的是连续空间,而且new而在申请空间失败时会抛异常,malloc会返回NULL。
2.对于自定义类型来说,new的原理: 即调用operator new开空间并且在申请的空间上执行构造函数,完成对象的构造。
delete的原理: 在空间上调用析构函数,完成对象中资源的清理工作,再调用operatir delete函数释放对象的空间。
其中new T[n]的原理: 调用operator new[]函数,operator new[]当中又是通过调用operator
new函数完成N个对象空间的申请,然后对这些空间执行N次构造函数。delete[]的原理:释放的对象空间上执行了N次析构函数,完成N个对象中资源的清理,调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。
小测试,画出下面的st与pst所在栈帧位置
class Stack
{
public:
Stack(int cap)
:_a(new int[cap])
,_top(0)
,_cap(cap)
{ }
private:
int* _a;
int _top;
int _cap;
};
int main()
{
Stack st(5);
Stack* pst = new Stack(5);
//....
delete pst;
pst = nullptr;
return 0;
}
需要注意这两个实例化出来的对象一个是在栈,一个在堆
,他们的声明周期是不一样的,但是他们的_a都是在堆上面,也证实了调用delete堆上的对象时,会先把它的资源(这里的_a)给清理了。这样才不会造成内存泄漏。
五、定位new表达式
定位new的作用是在已分配的原始内存空间中调用构造函数初始化一个对象
- 使用方式:两种方式差异即type有无默认构造函数!
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
定位new一般和内存池使用,正因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示构造函数进行初始化。
但我们这里没涉及内存池,所以我们换一个测试,打印一句话证明调用成功即可。
class Stack
{
public:
Stack(int cap)
:_a(new int[cap])
,_top(0)
,_cap(cap)
{
cout << "完成一次调用定位new\n";
}
~Stack()
{
free(_a);
_a = nullptr;
cout << "完成一次释放空间\n";
}
private:
int* _a;
int _top;
int _cap;
};
int main()
{
Stack* pst = (Stack*)malloc(sizeof(Stack));
new(pst) Stack(5);//这里调用Stack的构造需要传参
delete(pst);
pst = nullptr;
return 0;
}
六、malloc/free和new/delete的区别
我们从三点出发:特点和用法,底层原理区别,处理错误的方式。
- malloc/free是函数,new/delete是操作符(特点)
- malloc申请空间需要手动计算空间,new的话只需要空间的类型就可以(用法)
- malloc的返回值是void,new的返回值就是类型的指针(用法)。
- malloc失败返回NULL,delete失败抛异常(处理错误的方式)
- 申请自定义类型对象时,malloc/free只会开辟空间,new会开辟好空间调用构造函数,delete会先调用析构函数再释放空间。new在申请空间用到了malloc,delete用到了free。(底层原理区别)
调用new的时候我们都做了什么?
我们调用new相当于调用operator new先把空间开出来(抛异常),new当中实现了定位new显示调用构造函数,然后还利用了模板把返回值处理了。
编辑器的话指令少了定位new直接调用,但我们调用的时候就要显式调用。
总结
C++内存管理结束啦!!下一章节将会进行模板的讲述,讲述完之后就是STL,相当于C++终于可以入门啦!!!
还没有评论,来说两句吧...