C++内存管理:从malloc到new的进化之路

🔥个人主页:胡萝卜3.0
📖个人专栏: 《C语言》、《数据结构》 、《C++干货分享》、LeetCode&牛客代码强化刷题
⭐️人生格言:不试试怎么知道自己行不行
🎥胡萝卜3.0🌸的简介:


目录
在学习相关内容之前,我们先来做一道题目:
分析:
globalvar是一个全局变量,所以globalvar在静态区;static GlobalVar被static修饰,说明它是一个静态变量,那就在静态区;static Var在静态区;localVar是一个局部变量,局部变量的空间都是在桟帧中开辟的,所以在栈上;num1单独出现,表示数组首元素的地址,那它就是一个指针变量,在栈上;char2表示数组名,为首元素地址,字符串存储在数组中,这个字符串是可以被修改的,那么这个字符串就是在栈上,那char2就在栈上;*char2表示首元素,也在栈上;pchar3是一个指针变量,在栈上;const char* pchar3="abcd"中的const修饰的是指针指向的内容,这个内容不能被修改,这个字符串为常量字符串,在常量区,*pchar3就在常量区;ptr1中存放的是申请空间的地址,是一个指针变量,在栈上;*ptr1表示申请空间的首元素,空间是在堆上申请的,所以*ptr1在堆上。
选择题答案
globalVar在哪里?______
C. 数据段(静态区)
全局变量存储在静态区(数据段)
staticGlobalVar在哪里?______
C. 数据段(静态区)
static修饰的全局变量也存储在静态区
staticVar在哪里?______
C. 数据段(静态区)
函数内的static局部变量也存储在静态区
localVar在哪里?______
A. 栈
普通局部变量在栈上
num1 在哪里?______
A. 栈
数组名代表数组首地址,但数组本身在栈上
char2在哪里?______
A. 栈
局部字符数组,整个数组在栈上
*char2在哪里?______
A. 栈
数组首元素在栈上
pChar3在哪里?______
A. 栈
指针变量本身在栈上
*pChar3在哪里?______
D. 代码段(常量区)
指向的字符串"abcd"是常量字符串,在常量区
ptr1在哪里?______
A. 栈
指针变量本身在栈上
*ptr1在哪里?______
B. 堆
malloc分配的内存空间在堆上
图示解释:


正文
一、C语言中的内存管理
回顾前面在C语言中的学习,我们学习过malloc/calloc/realloc/free,我们通过题目来回顾一下:
【面试题】
1. malloc/calloc/realloc的区别?
malloc申请空间时不进行初始化,calloc申请空间时自动初始化为0;realloc是对空间的增容:如果原空间后面有空间,那直接在其后面直接扩容,新空间的地址和原空间的地址一样;如果原空间的后面空间不够,则新找一块空间,并将原空间中的数据拷贝到新空间中,再将原空间释放,然后返回新空间的地址
2. malloc的实现原理? glibc中malloc实现原理
其实C语言中的内存管理已经可以解决很多问题,那为什么C++中要搞个新的内存管理呢?ok,通过下面的学习,我们就知道原因了:
二、C++内存管理
2.1 初识new和delete
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式——通过new和delete操作符进行动态内存管理。

2.2 new和delete
2.2.1 new的使用


那这时候就有人想问了,如果我们想要初始化,该怎么做?

2.2.2 delete的使用
delete的使用分为两种情况:
情况1:释放单个
//申请一个int类型的空间,并初始化
int* p3 = new int(10);
//释放单个
delete p3;
情况2:释放多个
//申请一个空间大小为10的数组,int类型,并部分初始化
int* p4 = new int[10] {1, 2, 3, 4};
//释放多个
delete[] p4;
提醒:
释放多个也可以直接写delete p4; 但是不建议这么写
这里博主有一个问题:我们看啊,在C中有申请空间,还有扩容,刚才我们学习C++中的申请空间,那C++中有扩容吗?
其实是没有的,如果需要扩容,自己手动扩
我们看到其实new和delete的功能和malloc、calloc、free的功能很接近,那为什么祖师爷还要搞new和dlete呢?
其实是为了自定义类型,用malloc可以申请一个自定义类型的空间,那该怎么初始化呢?这是不是一个问题,那为了解决这个问题,祖师爷就搞出了new和delete

new和delete就可以解决这样的问题!!!

我们看到:new和delete的最大特点就是自动调用构造和析构,而malloc和free不会!!!以后主要使用new和delete
2.2.3 new delete的失败
new 和 delete也会失败,那他们的失败和malloc free的失败是一样的吗?
其实是不一样的,new申请空间失败不会返回空,而是会抛异常

那我们该怎么捕获异常呢(ok,这里博主简单演示一下,后面会具体介绍):
void func()
{
int i = 1;
int* ptr = nullptr;
do {
if (i == 495)
{
int x = 0;
}
ptr = new int[1024 * 1024];
cout << i++ << ":" << ptr << endl;
} while (ptr);
cout << i++ << ":" << ptr << endl;
}
int main()
{
try
{
func();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
通过上面的try,catch就可以捕获异常。
2.3 new和delete的底层
new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。operator new 和operator delete是系统提供的全局函数
2.3.1 new的底层
new开空间的步骤:
- 先调用operaotr new 在堆上开空间(底层还是调用malloc)
- 调用构造
先调用operaotr new 在堆上开空间(底层还是调用malloc)——这是什么意思?
我们来看一下底层代码:
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
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申请空间成功时直接返回;申请空间失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
所以说new的底层还是调用malloc,可以认为new是对malloc的包装
2.3.2 delete的底层
delete的步骤:
- 调用析构
- 调用operator delete
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)
operator delete: 该函数最终是通过free来释放空间的!!!
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
三、new和delete的实现原理
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来释放空间
四、malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc/free是函数,new/delete是操作符
- malloc 失败返回的是nullptr,需要我们进行判断;new失败抛异常,不需要进行判断,但要捕获异常
- malloc只开空间,不初始化;new开空间,可以初始化
- malloc申请空间时需要手动计算空间大小;new只需在其后面跟上类型即可,如果是多个对象,[ ]中指定个数即可
- malloc 的返回值类型为void* ,需要强转;new不需要,后面跟的就是类型
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放
上面的区别不建议背,理解即可!!!
更多推荐

所有评论(0)