✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源
Love is a choice. It is a conscious commitment. It is something you choose to make work every day with a person who has chosen the same thing.
- 爱是一种选择。这是一种有意识的承诺。这是你选择每天与一个选择同样事情的人一起工作的东西。
文章目录
- 📘前言
- 📘正文
- 📖内存分布
- 🖋️五大分区
- 🖋️图解
- 📖重温
- 🖋️malloc/calloc/realloc
- 🖋️free
- 📖初识
- 🖋️new
- 🖋️delete
- 🖋️特点
- 📖探究
- 🖋️封装实现
- 🖋️代码展示
- 📖new/delete 实现步骤
- 🖋️内置类型
- 🖋️自定义类型
- 📖定位new
- 🖋️应用场景
- 📖注意事项
- 📘总结
📘前言
C++
中的内存管理机制和C语言
是一样的,但在具体内存管理函数上,C语言
的malloc
已经无法满足C++
面向对象销毁的需求,于是祖师爷在C++
中新增了一系列内存管理函数,即 new
和 delete
著名段子:如果你还没没有对象,那就尝试 new
一个吧
📘正文
将内存分成不同区域是为了实现更好的管理,比如在我们现实生活中,一幢房子会被分为客厅、厨房、卧室、卫生间等区域,目的是为了使我们生活更加方便、空间利用更加合理,计算机也是如此,更何况是空间非常珍贵的内存,因此在我们的程序中存在不同内存分区
📖内存分布
在程序中存在五大分区,各个分区各司其职,比如我们耳熟能详的栈区、堆区、静态区
🖋️五大分区
栈区:
栈
又称做堆栈
,用于存储非静态局部变量、函数参数、返回值等,栈
的空间是向下增长的
内存映射段:
内存映射段
是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
堆区:
堆
用于程序运行时动态内存分配,堆是可以上增长的,我们的动态内存就是在堆
上申请的
数据段:
数据段
中负责存储全局数据和静态数据
代码段:
代码段
中存储可执行的代码/只读常量
注意: 内存中还存在内核空间
,但我们普通用户代码无法读写
🖋️图解
通过具体代码展示具体分布如下
📖重温
先简单回顾下C语言
中的动态内存管理
🖋️malloc/calloc/realloc
C语言提供了三种动态内存管理函数
malloc
:申请指定大小的空间
int* pi = (int*)malloc(sizeof(int) * 1); //申请一个整型
double* pd = (double*)malloc(sizeof(double) * 2); //申请两个浮点型
char* pc = (char*)malloc(sizeof(char) * 3); //申请三个字符型
注意: malloc
申请的空间都是未初始化的,即被编译器置为随机值
calloc
:将申请的空间初始化为 0
int* pi = (int*)calloc(1, sizeof(int)); //申请一个整型
double* pd = (double*)calloc(2, sizeof(double)); //申请两个个浮点型
char* pc = (char*)calloc(3, sizeof(char)); //申请三个字符型
注意: calloc
参数列表与malloc
不同,同时calloc
申请的空间会被初始化为 0
realloc
:对已申请的空间进行扩容
int* tmp = (int*)realloc(pi, sizeof(int) * 10); //将 pi 扩容为十个整型
pi = tmp; //常规使用方法
注意: 我们要对所有的申请函数进行空指针检查,预防野指针问题
堆区
的空间由我们管理,编译器很信任我们,因此我们要做到有借有还,再借不难
凡是动态开辟的空间,用完后都需要释放
🖋️free
C语言
提供的空间释放函数是free
free(tmp); //此时tmp指向pi扩容后的空间,释放tmp就行了
tmp = pi = NULL; //两者都需要置空
free(pd);
pd = NULL;
free(pc); //只要是动态开辟的,都需要通过 free 释放
pc = NULL;
注意: 只有动态开辟的空间才能使用 free
,同时一块空间不能释放两次。我们在 free
后通常会把指针置空
关于C语言
动态内存管理更多细节可以看看这篇文章:《C语言动态管理》
这里就不再阐述
C语言 中管理函数只能对内置类型使用,而 C++ 中存在很多自定义类型,常规 malloc 等函数无能为力
📖初识
出现了新的关键字:new
和 delete
,它们也有很多形式和使用细节
🖋️new
使用:
int* pi = new int; //申请一个整型
double* pd = new double(3.14); //申请一个浮点型并初始化为 3.14
char* pc = new char[5] {'H', 'e', 'l', 'l', 'o'}; //申请五个字符型,并分别初始化为 Helloc
注意:
new
与malloc
等不同,不需要进行空指针检查,也不需要进行类型转换new
的使用极其简单
特点:
new
可以用于自定义类型- 动态开辟时,会调用自定义类型的
构造函数
//假设存在日期类
Date* ptr = new Date[5]; //申请五个日期类,这些类都在堆上
下面来看看C++
中的内存释放函数
🖋️delete
形式:
delete 指针
delete[] 指针
使用:
- C语言中的 free 可以用于释放所有动态申请函数,而 C++ 不行,申请与释放需要配套使用
int* pi = new int;
delete pi; //直接释放
double* pd = new double[5];
delete[] pd; //释放五次
Date* ptr = new Date[5];
delete[] ptr; //释放五次,即调用五次日期类的析构函数
注意:
- 需要配套使用,
new int
搭配delete
,而new int[]
需要搭配delete[]
特点:
delete
可以用于自定义类型- 调用销毁时,会先调用自定义类型的
析构函数
🖋️特点
C语言
和C++
动态内存管理函数的最大区别是: 是否会调用自定义类型的构造函数和析构函数
C语言
明显不会,毕竟那时候还没有这些概念,而 C++
作为面向对象的语言,调用构造与析构函数是必然的
C语言
中的申请函数不能通过C++
的释放函数进行释放,同理C++
的申请空间也不能通过C语言
的释放函数进行释放,比如下面这些情况是不合理的,可能引发问题
int* cPi = (int*)malloc(sizeof(int));
delete cPi; //不合理的操作
int* cppPi = new int;
free(cppPi); //这样也是不合理的
Date* ptr = new Date;
free(ptr); //此时会报错,因为 free 并不会调用析构函数
切记,申请与释放要配套使用
📖探究
为何C++
中的动态内存管理函数能做到调用构造
和析构
函数呢?
- 这是因为我们也是调用的其他函数,正是得益于
C++
中的封装
🖋️封装实现
new
和 delete
是用户进行动态内存申请和释放的 操作符
,它们在实现时会去调用真正的全局函数
operator new
与 operator delete
,具体调用情况如下所示:
new
与 new []
new
调用operator new
new []
调用operator new[]
delete
与 delete []
delete
调用operator delete
delete []
调用operator delete[]
注意: operator new[]
最终是调用 operator new
,operator delete[]
最终也是调用 operator delete
所以严格来说,operator new
和 operator delete
才是我们探讨的主角
🖋️代码展示
先来看看 operator new
的代码实现
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECLoperatornew(size_tsize)_THROW1(_STDbad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
//如果申请内存失败了,这里会抛出bad_alloc类型异常
staticconststd::bad_allocnomem;
_RAISE(nomem);
}
return(p);
}
//代码源自:比特教育科技 https://www.bitejiuyeke.com/index
可以看到,其实 operator new
就是通过对 malloc
的封装实现的,不过进行了改进,当对象为自定义类型时,会去调用它的构造函数,并且当开辟失败时,会抛出异常
再来看看 operator delete
的代码实现
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
//代码源自:比特教育科技 https://www.bitejiuyeke.com/index
operator delete
的代码中也有 free
的影子,当释放对象为自定义类型时,会调用它的析构函数
📖new/delete 实现步骤
下面再来看看两者具体的实现步骤
🖋️内置类型
对于内置类型来说,使用 malloc/free 和 new/delete 没什么区别
🖋️自定义类型
对于自定义类型,new/delete 的实现步骤如下:
new
- 调用
operator new
申请空间 - 在申请的空间上调用构造函数
delete
- 在空间上调用析构函数
- 再调用
operator delete
释放空间
new []
- 调用
operator new[]
函数,根据数值N,调用N次operator new
函数申请空间 - 在申请的空间上调用N次构造函数
delete []
- 在申请的空间上调用N次析构函数
- 调用
operator delete[]
函数,然后由函数再调用operator delete
释放空间
📖定位new
定位new
是 new
的新用法
目的:
- 对已开辟而未初始化的空间进行初始化
形式:
new(指针)构造函数
//定位new
Stack* ptr = (Stack*)malloc(sizeof(Stack)); //malloc 不会调用构造函数,此时未初始化
new(ptr)Stack(); //通过定位new初始化对象
🖋️应用场景
定位new
可以用在内存池这个项目中
- 向堆中申请一块定额空间,此时空间未初始化
- 此时就需要通过
定位new
来进行初始化
📖注意事项
开辟与释放需要配对使用
malloc/calloc/realloc
搭配 free
new
搭配 delete
new []
搭配 delete []
📘总结
以上就是关于 C++
内存管理的全部内容了,记住一点就够了:认识 new
,配对使用。
如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!
如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正
相关文章推荐
类和对象实操
类和对象实操之【日期类】
===============
类和对象合集系列
类和对象(下)
类和对象(中)
类和对象(上)
===============
C++入门必备
C++入门基础