【c++初阶】C++内存管理
- 电脑硬件
- 2025-09-16 01:57:01

目录
C++的内存管理
new:
new+类型
申请多个:
单纯开空间不初始化:
初始化写法:
单个:
多个数据:
new的优势体现:
delete:
复杂场景理解:
new,delete的底层原理:
定位new
定位new应用:内存池技术
总结:malloc/free和new/delete的区别
C++的内存管理
C++的内存管理和C语言基本是一致的,
33
划分区域的原因:不同的数据存储要有不同的性质,满足不同数据的不同的存储需求。
临时用:栈区
动态使用需求:静态区(常见的数据结构,需要动态开辟一些空间)
只读数据:常量 可执行代码(二进制指令) 常量区数据一般不可修改:因为常量区代码一般是编译好的指令,防止运行程序被修改,安全性就不可靠了。
平时写的代码是存在磁盘上的。
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = { 1, 2, 3, 4 }; char char2[] = "abcd"; const char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof(int) * 4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); free(ptr1); free(ptr3); }globalVar在哪里?全局变量在静态区 staticGlobalVar在哪里?静态变量在静态区
不同的是,二者的链接属性不一样,static GlobalVal只有在当前文件可以用。
staticVar在哪里?静态变量在静态区存储,不一样的是,这个变量只有在调用这个函数的时候才初始化,但是三个变量的生命周期在整个函数期间都可以用。
localVar在哪里?临时变量在栈区
num1 在哪里?局部数组在栈区
难度:
char2在哪里?在栈区
*char2在哪里?首元素地址,在栈区
pChar3在哪里?是一个指针在栈区
*pChar3在哪里?常量区,常量区不可修改,所以加上了const,
ptr1在哪里?栈区
*ptr1在哪里?堆区_
补充:
const char * p1 限定的是p1指针指向的内容
char * const p1 修饰的是指针p1本身。
new:malloc 和calloc区别:calloc会将空间初始化为0
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new+类型 int* p1 = new int; 申请多个: int* p2 = new int[10]; 单纯开空间不初始化: 初始化写法: 单个: int* p3 = new int(1); 多个数据: int* p4 = new int[3] {1, 2, 3};多个数据也可以部分初始化,后面的数据默认初始化为0:
int* p5 = new int[10] {1, 2, 3};释放,是new的就用delete释放,delete一定要和new匹配起来,new用方括号,delete也要使用方括号。匹配如下:
int* p1 = new int; delete p1; int* p2 = new int[10]; delete[] p2; int* p3 = new int(1); delete p3; int* p4 = new int[3] {1, 2, 3}; delete[] p4; int* p5 = new int[10] {1, 2, 3}; delete[] p5;设计意义:解决类申请空间的初始化:
构造一个类如下:
class A { public: A(int a = 0) : _a(a) { cout << "A():" << this << endl; } ~A() { cout << "~A():" << this << endl; } private: int _a; }; A* p6 = (A*)malloc(sizeof(A));如何初始化这块空间。
如果这样初始化:
malloc只可以解决内置数据类型的初始化,自定义类型的初始化是没有办法的。
如果是new:
A* p7 = new A;先开空间再调用构造函数。解决了问题,甚至可以显示调用构造函数:
A* p7 = new A(1); new的优势体现:比如在链表中的应用:
struct ListNode { int _val; ListNode* _next; ListNode(int val) :_val(val) ,_next(nullptr) {} }; int main() { //创建新节点 ListNode* n1 = new ListNode(1); ListNode* n2 = new ListNode(1); ListNode* n3= new ListNode(1); }就不用再像C语言结构体还得去创建节点初始化节点了。
多个对象初始化:
①有名初初始化:
A a1(1); A a2(1); A a3(1); A* p8 = new A[3]{ a1,a2,a3 };//有名对象初始化②匿名初始化
A* p9 = new A[3]{ A(1),A(1) ,A(1) };③隐式类型转化初始化:先构造再拷贝构造,编译器优化只有构造
A* p10 = new A[3]{ 3,3,3 };new的本质是开空间+调用构造函数初始化
delete:delete作用:调用析构函数+释放空间
如果是内置类型的申请和释放除了用法上没有去吧,melloc失败了会返回空指针。二者在自定义类型上才会有区别。
new失败了会抛出异常:连续开或者开辟内存很大,系统已经没有了,异常必须被捕获。测试代码
如下:
char* p1 = new char[1024 * 1024 * 1024]; cout << (void*)p1 << endl; char* p2 = new char[1024 * 1024 * 1024]; cout << (void*)p2 << endl;char * 的指正没有办法直接cout,会先识别为字符串,会遇到斜杠0才结束,用printf打印或者转换成上面的样子。
异常在c++中必须被捕获使用:try catch
try { char* p1 = new char[1024 * 1024 * 1024 * 1024 * 1024 * 1024]; /*cout << (void*)p1 << endl;*/ cout << p1 << endl; char* p2 = new char[1024 * 1024 * 1024 * 1024]; cout << (void*)p2 << endl; } catch (const exception& e) { cout << e.what() << endl; }出错了以后直接跳到捕获的地方。异常是会影响执行流,抛异常过后,后续的代码不会执行,直接跨函数到catch,异常对象里面就会包含异常信息。、
复杂场景理解:看如下代码:
如何理解:
首先我们知道的是new的作用是开辟空间并调用构造函数。所以对于new Stack相当于new一个匿名对象:先开辟一个栈大小的空间,再调用构造函数
然后调用delete时先释放_a所指向的空间,再释放类对象,这样才避免野指针。
所以会先调用析构函数清理栈的资源,然后再释放类对象的空间。这就是delete的作用过程:先调用析构函数再释放空间。如果这里使用free就直接会将类对象释放,a锁指向的空间不被释放就会造成内存泄漏。
new,delete的底层原理:因为开空间在堆上申请,C++又是兼容C的,所以没有必要单独的做一个接口,所以new底层会转换去调用malloc.但是不会直接去调用。而是实现了两个全局函数,封装了malloc和free:new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是 系统提供的全 局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间 失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否 则抛异常。 */ void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0)通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。 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; }这两个函数是库里的全局函数,我们可以使用,底层封装了malloc和free,这两个函数我们是可以用的,效果和malloc与free一样。不会调用构造函数与析构函数。
这两个函数的作用:封装了malloc和free实际就是未来处理失败抛异常的问题这样更符合面向对象的操作特点。new和delete的操作是首先申请对象空间然后再调用构造函数。new和delete是两个操作符,调用构造函数可以转化成指令调用函数,申请空间却不会调用malloc因为malloc申请空间失败返回的是0.但是作为一门面向对象的语言,是不会靠返回值来指导工作的,这像面向过程。需要抛出异常。所以在调用malloc的时候再封装一层,抛出一层,符合面向对象的变成特点。
这里相当于new了一个栈的数组,这个栈是需要连续的,所以调用一次operatenew,一次开辟十个栈对象的大小。严格来说实际上调用的operate new[]
operate new[]内部再去调用operate new;在调用十次构造函数。而实际开开辟多开辟4个字节,记录开辟的个数,为delete做准备:
然后调用10次析构函数,调用operate delete[]:
这里可以观察一下,如果delete 不匹配的情况:
程序会崩溃:
当我们换一个类并且我们不写析构函数时:
程序没有挂,连内存泄漏都没有。当我们写了析构函数:
原因:
因为我们正确释放空间的位置因为多开辟了几个字节所以不能在p3处释放。第一个行是因为是内置类型成员,析构函数调用不调用无所谓,所以释放的位置是准确的。这是编译器优化的后果,所以一定要配对使用。所以内置类型使用free也是可以的。
定位new构造函数不可以显示调用,是自动调用的。但是析构函数可以显示调用。
如果想要显示调用构造函数需要使用到定位new:对一块空间调用构造函数进行初始化。
使用格式: new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如 果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
A* p1 = new(A); new(p1)A(1);但是,一般使用的时候是不会显示调用的。
定位new应用:内存池技术当我们频繁的调用new来申请小对象时候,由于底层已经写好了,那么调用malloc频繁的去开辟小空间,效率会很地下,所以就有了内存池技术。一次申请一大片空间,每次申请内存时就不用去访问堆了。而是访问内存池。而,对于内存池这个需要我们自己创造,这个时候就无法直接调用new只能调用operate new,然后显示的调用构造函数利用定位new来实现初始化。
总结:malloc/free和new/delete的区别malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地 方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需 要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成 空间中资源的清理
【c++初阶】C++内存管理由讯客互联电脑硬件栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【c++初阶】C++内存管理”