C++内存管理
为什么出现new🤔
C语言中,开辟空间一般用malloc,对于面向过程的语言malloc倒是够用,但在C++中,对象的出现让malloc不再拥有以前的地位——需要强转类型,出现问题只能我们自己去发现,并且对于自定义对象不是那么友好,malloc不会去调用对象的构造函数,很难完成初始化。所以在C++中出现了新的开空间操作符new,以及释放空间操作符delete。
new和delete用法🧐
内置类型用new不会自动初始化,比malloc用法简化,功能一致。
注意:new多个空间,delete的时候需要加上方括号“[ ]”,理由是delete[]在有析构函数时会多开字节用于存储new的次数,而delete不会多开字节,所以用delete去清除多个空间时,与delete[]的地址起始位置不同,从而报错。
所以我们手动初始化,大括号内如不写就自动初始化为0。
如果我们new一个自定义类型,它会去开辟空间和调用拷贝构造,delete会调用析构函数+释放空间。
new的出现让我们创建一个链表变得更加简单,不再需要写那么多代码
底层实现🧐
new和delete底层是通过调用operator new和operator delete实现的,它俩不是重载,而是库函数,并且底层也是靠malloc和free实现的。以下源码可以参考:
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 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)
并且,我们也可以从汇编代码中发现这一点,析构函数和operator delete都是函数调用。
抛异常🧐
当new失败时会抛出异常,需要用try catch语句进行异常捕获,可以打印出因为什么导致开空间失败。当开空间失败了,抛出异常后会终止之后的代码,直接到catch语句中。
实现原理🧐
内置类型:
对于内置类型,new、delete与malloc、free基本相似,不过new和delete创建、释放的是单个空间,new[]和delete[]才是多个空间,并且new失败会抛异常,malloc是返回NULL。
自定义类型:
new原理:
调用operator new函数申请空间,在申请的空间上执行构造函数。
delete原理:
在空间上执行析构函数,调用operator delete函数释放对象空间。
new[N]原理:
调用operator new[]函数,在该函数中实际调用operator new函数N次完成N个对象的空间申请,然后执行N次构造函数。
delete[N]原理:
在释放的对象空间上执行N次析构函数,调用operator delete[]释放空间,实际是调用N次operator delete。
定位new表达式(placement-new)🧐
定位new表达式是在已分配原始内存空间中调用构造函数初始化一个对象,即显示化调用一个构造函数。一般用于内存池,因为内存池分配出的内存没有初始化,所以需要用自定义对象去显示调用。
malloc/free与new/delete🧐
相同的是,它们都是在栈上开辟空间,并且需要手动释放,区别在于:
- malloc失败返回的是NULL,new是抛异常
- malloc成功返回的是void*,需要强转后使用,new后面跟的什么空间类型就是什么
- malloc、free是函数,new、delete是操作符
- malloc、free不会调用构造和析构,new和delete会调用
- malloc需要手动计算大小,new直接指定大小
- malloc无法初始化,new可以初始化
内存泄漏🧐
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而 造成了内存的浪费。
长期的内存泄漏,会导致操作系统,后台服务等响应越来越慢,直到崩溃。在vs下可以使用_CrtDumpMemoryLeaks()函数来进行简单的检测,不过只能告诉你是否有泄漏,如想进一步检测可以下载相应工具。
int main() { int* p = new int[10]; _CrtDumpMemoryLeaks(); return 0; }
内存泄漏一般有两种:堆内存泄漏和系统资源泄露,堆内存泄漏指的是我们手动分配的空间没有delete释放掉,那么这块空间一直存在,我们也无法使用,造成泄漏。系统资源泄露指的是程序使用分配的资源,如套接字,文件描述符,管道等没有使用对应函数释放掉,导致系统资源浪费,系统执行不稳定。
所以,我们一定要养成良好的编码习惯,申请的空间使用完后要注意释放。
结尾👍
以上便是本篇博客全部内容,如果有疑问或者建议都可以私信笔者交流,大家互相学习,互相进步!🌹