前言
一、内存区域划分
二、C++的内存管理方式
2.1 对内置类型
2.2 对自定义类型
三、new和delete的底层实现
四、new和delete的原理
五、定位new
六、malloc/free和new/delete
前言
在C++中,内存管理是不可避免的一门必修课。C++对内存的自由度使其获得了更高的性能,以及更高的难度。内存泄漏往往是每个C++学习者绕不开的错误, 而内存管理的水平高低也能看出一个编程者的能力。
在C语言中,我们学习了malloc、calloc、realloc和free,对C语言的内存管理也有了大致的接触。
本文中我们来学习C++中的内存管理
一、内存区域划分
C++中,程序的内存区域从低地址到高地址划分如下:
- 代码段:存储可执行程序的代码和只读常量
- 数据段:存储已初始化的全局变量和静态变量
- 堆:用于程序运行时动态内存分配,从低地址向高地址增长
- 栈:又叫堆栈,存储非静态局部变量/函数参数和返回值等,从高地址向低地址增长
例如:
globalVar是全局变量,staticGlobalVar是静态全局变量,存储在数据段中;
staticVar是静态局部变量,存储在数据段中;
localVar、num1、char2、pChar3和ptr1都是局部变量,存储在栈中;
*char2是在栈帧中的空间,存储在栈中;*pChar3是常量字符串abcd的第一个字符,在代码段中;
malloc动态开辟出的空间存储在堆上。
二、C++的内存管理方式
2.1 对内置类型
C++兼容C语言,所以C语言的内存管理方式在C++中可以正常使用
相比C语言使用malloc和free等函数进行内存管理,C++提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理
以上是C语言和C++动态申请一个类型大小的空间、多个类型大小的空间和内存释放的方式
实际上,面对内置类型,用malloc和new没有本质的区别,最大的区别在于:new可以初始化
在默认情况下,new和malloc一样不会对内置类型进行初始化,但是我们可以提出需求
注意区分初始化和申请多个元素,一个是圆括号一个是方括号
我们new一个数组时也可以进行初始化,例如:
C++不推荐使用malloc和free,我们最好使用new和delete,并且记得务必匹配使用。
对于内置类型,malloc和new区别不大。new是为了自定义类型而生的。
2.2 对自定义类型
我们在创建自定义类型对象的时候,需要调用析构函数,销毁时需要调用构造函数
而如果我们使用malloc和free的话,是不会调用这两个函数的。
使用new来为自定义类型对象申请空间,编译器才会调用构造函数为对象初始化;用delete为自定义类型对象释放空间,才会调用析构函数。
三、new和delete的底层实现
new和delete并不是函数,而是用户进行动态内存申请和释放的操作符。
但是其底层还是需要调用函数。
new在底层调用operator new这个函数来申请空间,delete在底层调用operator delete函数来释放空间。operator new和operator delete是系统提供的全局函数。
注意:这两个函数不是new和delete的重载函数!!!
虽然函数名中带operator,但并不是重载函数,具有很强的误导性。
我们来看看这两个函数的实现:
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);
}
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;
}
通过上面两个全局函数的实现,可以看出operator new实际上也是通过malloc来申请空间的,如果malloc申请空间成功就直接返回,如果失败则执行用户提供的应对措施,如果用户提供该措施则继续申请空间,否则抛出异常。
operator delete最终也是通过free来释放空间的。就像引用的底层也是用指针的方式实现的。
需要注意,构造函数和析构函数不是通过这两个函数来调用的。
四、new和delete的原理
(1)new的原理
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
(2)delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
(3)new T[N]的原理
- 调用operator new[]函数,而operator new[]函数实际上又会调用operator new函数完成N个T类型对象的空间申请
- 在申请的空间上执行N次构造函数
(4)delete[]的原理
- 在空间上执行N次析构函数,完成N个对象的资源清理
- 调用operator delete[]函数,而operator delete[]函数又会调用operator delete函数来释放空间
五、定位new
定位new表达式用于在已分配的原始内存空间中调用构造函数初始化一个对象
大部分情况下,我们直接使用new来给对象分配空间
但是有时候需要进行性能优化,我们会直接从内存池中拿空间,使用malloc开空间
平时我们需要分配空间时从操作系统——堆上开空间,每次有需求就要开一次,但是内存池一次从堆上拿走一个内存块(大块空间),就不需要我们重复的去申请,减少了与堆的交互,提升了效率
如果是自定义的对象,对于malloc出来的空间,则需要使用定位new来显式的调用构造函数进行初始化
使用格式:
构造函数不需要传参时:new(指针)类名
构造函数需要传参时:new(指针)类名(参数)
例如:
或者:
销毁的方式:
六、malloc/free和new/delete
malloc/free和new/delete的共同点在于:都是从堆上申请空间,并且需要用户手动释放
不同点在于:
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以进行初始化
- malloc申请空间时需要自行计算要开的空间大小,new只需要空间类型和元素个数
- malloc的返回类型为void*,需要强制转换类型,new不需要
- malloc申请空间失败时返回空指针,需要判空,new失败时抛出异常,需要捕获
- 为自定义类型对象申请空间时,malloc和free不会调用构造函数和析构函数,而new和free会调用
如果文章有误,欢迎在评论区指出