hello,各位小伙伴,本篇文章跟大家一起学习《C++:new与delete》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
文章目录
:rocket: C++内存管理:airplane: 初识`new`和`delete`:airplane: `new`和`delete`底层逻辑:airplane: 数组`new[]`和`delete[]`注意事项:airplane: `new`和`delete`的实现原理:airplane: 定位new表达式(placement-new) (现阶段了解一下即可):airplane: malloc/free和new/delete的区别:airplane: 关于内存泄漏1.**:fire:内存泄漏的危害!!!**2.:fire:内存泄漏的分类(了解一下即可)3.:fire:如何检测内存泄漏(了解一下)
? C++内存管理
我们学过malloc、calloc、realloc、free
,这些都是我们在C语言时学的,那么C++又引入了什么呢?
✈️ 初识new
和delete
没错,就是new
和delete
,他们都是用于动态内存管理的操作符。
new
用于在堆内存中动态地分配内存空间,通常用于创建对象或数组。delete
用于释放由 new
分配的内存空间,以防止内存泄漏。 以下是它们的基本用法:
// 使用 new 分配单个对象的内存空间int* ptr = new int;// 使用 new 分配数组的内存空间int* arr = new int[10];// 使用 delete 释放单个对象的内存空间delete ptr;// 使用 delete[] 释放数组的内存空间delete[] arr;
?在使用 new
分配内存后,必须使用 delete
或 delete[]
来释放这些内存,否则会导致内存泄漏。
当然,我们还可以在new
时进行初始化,如:
int* ptr = new int(1);int* arr = new int[10]{1,2,3,4,5,6,7,8,9,0};
但是对于数组,我们一般用循环初始化,毕竟数组的元素个数可多可少
?同样是在堆上进行操作,那么new
和delete
相比于malloc
和free
又有什么优势呢?
构造函数和析构函数的调用:
使用new
分配的内存会调用对象的构造函数,而释放内存时会调用对象的析构函数。使用 malloc()
分配的内存不会调用对象的构造函数和析构函数,它仅分配内存空间。因此,如果你在 C++ 中使用类,尤其是含有构造函数和析构函数的类,应该优先使用 new
和 delete
,以确保对象的生命周期正确管理。 类型安全:
new
和 delete
是类型安全的,它们会自动为分配和释放的内存调用正确的构造函数和析构函数。malloc()
和 free()
不是类型安全的,它们仅仅操作指针和内存地址,不关心数据类型。 数组分配:
new[]
和 delete[]
用于分配和释放数组,它们可以正确处理数组的元素。malloc()
和 free()
无法直接处理数组,你需要手动跟踪分配的内存大小,并确保正确释放。 内存对齐:
new
和 new[]
会保证分配的内存按照对象的对齐要求进行,而 malloc()
则不保证内存对齐。内存对齐在某些情况下可能会影响性能和内存访问的正确性。 总的来说,在 C++ 中,如果你在使用类或动态分配数组,最好使用 new
和 delete
,因为它们更符合 C++ 的对象模型,并且提供了更多的类型安全和便利性。
还有
new
不需要强制类型转换new
不需要计算需要多大内存空间 ✈️ new
和delete
底层逻辑
? operator new与operator delete函数
new
和delete
是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new
在底层调用operator new全局函数来申请空间,delete
在底层通过operator delete全局函数来释放空间。
看下述代码:
class A{public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}private:int _a;};int main(){A* ptr = (A*)operator new(sizeof(A));new(ptr)A;// 调用构造函数,不能直接调用ptr->~A();// 调用析构函数,能直接调用return 0;}
operator new该函数实际通过malloc
来申请空间,当malloc
申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
operator delete该函数最终是通过free
来释放空间的。
new(ptr)A;// 调用构造函数,不能直接调用ptr->~A();// 调用析构函数,能直接调用
由于operator delete并不会调用构造函数和析构函数,要手动操作。
调用构造函数时也可以传参:
new(ptr)A(1);
✈️ 数组new[]
和delete[]
注意事项
?对于delete[]
和delete
一定要对应使用
对于动态分配的内存,在释放时必须使用与分配时相对应的操作符。
new
分配的单个对象,应使用delete
释放内存。对于使用new[]
分配的数组,应使用delete[]
释放内存。 使用delete
释放使用new[]
分配的内存或使用delete[]
释放使用new
分配的内存都会导致未定义的行为。这是因为new
和new[]
分别调用了不同的构造函数,因此释放时必须相应地使用delete
或delete[]
来调用相应的析构函数。
例如,如果你这样分配内存:
int* a= new int;int* arr= new int[10];
则应该这样释放内存:
delete a;delete[] arr;
这样做可以确保内存被正确释放,避免内存泄漏和其他潜在的问题。
?对于自定义类型,还有一些要注意的点
我们来看下述代码1:
class A{public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}private:int _a;};int main(){A* ptr = new A[10];return 0;}
大家猜猜系统为开辟的数组一共用了多大的内存(32位机器)
答案是:44字节
我们再来看看这个代码2:
int main(){int* arr = new int[10];return 0;}
大家再猜猜系统为开辟的数组一共用了多大的内存(32位机器)
答案是:40字节
这就奇怪了呀,明明都是开10个int
类型的数组,为什么?
这是因为自定义类型中有析构函数,那么编译器会认为这个析构函数是需要使用的,但是要析构多少次,就要多开4个字节(32位机器地址大小为4个字节,64位地址大小为8字节)来存储有多少个元素需要进行析构,如图:
那么如果该自定义类型没有析构函数,是不是就变成40字节了?
答案是:正确的
✈️ new
和delete
的实现原理
?对于内置类型:
如果申请的是内置类型的空间,new
和malloc
,delete
和free
基本类似,不同的地方是:new/delete
申请和释放的是单个元素的空间,new[]
和delete[]
申请的是连续空间,而且new
在申请空间失败时会抛异常,malloc
会返回NULL。
?对于自定义类型:
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来释放空间✈️ 定位new表达式(placement-new) (现阶段了解一下即可)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
class A{public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}private:int _a;};int main(){// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行A* ptr = (A*)malloc(sizeof(A));new(ptr)A(10);// 注意:如果A类的构造函数有参数时,此处需要传参,如我所写free(ptr);A* ptr = (A*)operator new(sizeof(A));new(ptr)A(10);operator delete (ptr);return 0;}
✈️ malloc/free和new/delete的区别
?malloc/free
和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
malloc
和free
是函数,new和delete是操作符malloc
申请的空间不会初始化,new可以初始化malloc
申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可malloc
的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型malloc
申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常申请自定义类型对象时,malloc/free
只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理 ✈️ 关于内存泄漏
1.?内存泄漏的危害!!!
内存泄漏是指程序在动态分配内存后,无法再次访问或释放该内存,导致程序持续占用内存而不释放。内存泄漏可能会导致以下危害:
内存资源浪费:内存泄漏会导致程序持续占用内存而不释放,随着时间的推移,系统的可用内存将逐渐减少,可能导致系统性能下降甚至崩溃。
程序性能下降:随着内存泄漏的累积,系统可用内存减少,可能会导致频繁的内存交换(如果系统使用虚拟内存),增加了页面调度的开销,降低了程序的整体性能。
程序崩溃:当程序持续占用内存而不释放,最终可能导致系统内存耗尽,触发操作系统的内存管理机制,导致程序崩溃或被系统强制终止。
资源管理混乱:内存泄漏可能会导致程序中的资源管理混乱,例如无法及时释放打开的文件、数据库连接或网络连接,进而影响系统的稳定性和可靠性。
难以调试和定位问题:内存泄漏通常是程序中较为隐蔽的问题,随着内存使用的增加,程序的运行速度可能会变慢,但不一定会立即导致崩溃或错误,因此难以定位和调试。
为了避免内存泄漏的危害,程序员应该养成良好的内存管理习惯,及时释放不再需要的内存,并使用内存检测工具(如Valgrind、AddressSanitizer等)来帮助检测和修复潜在的内存泄漏问题。
小伙伴们一定要记得噢!!!
2.?内存泄漏的分类(了解一下即可)
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统
资源的浪费,严重可导致系统效能减少,系统执行不稳定。
3.?如何检测内存泄漏(了解一下)
在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出
了大概泄漏了多少个字节,没有其他更准确的位置信息。
int main(){int* p = new int[10];// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();return 0;}// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置Detected memory leaks!Dumping objects ->{79} normal block at 0x00EC5FB8, 40 bytes long.Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CDObject dump complete.
咱目前只是了解一下,无需理解
因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的
可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内
存泄漏检测工具处理的。
好啦,这篇文章就到此结束了
所以你学会了吗?
好啦,本章对于《C++:new与delete》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!
如你喜欢,点点赞就是对我的支持,感谢感谢!!!