【C++初阶】内存管理总结(从 C 语言 malloc 到 C++ new/delete)

【C++初阶】内存管理总结(从 C 语言 malloc 到 C++ new/delete) 文章目录前言一、C/C 内存区域划分1. 五大内存区域2. 变量位置判断二、C 语言动态内存管理malloc/calloc/realloc1. 函数对比三、C 内存管理new 和 delete1. new/delete 操作内置类型2. new/delete 操作自定义类型四、底层揭秘operator new 与 operator delete1. operator new 是什么2. operator delete 是什么3. 关系链五、new 和 delete 的实现原理1. 内置类型2. 自定义类型总结前言本文从 C/C 内存分区讲起简单回顾 C 语言malloc/calloc/realloc再深入讲解 C new/delete的用法、底层原理。一、C/C 内存区域划分1. 五大内存区域区域存储内容特点栈Stack非静态局部变量、函数参数、返回值等函数结束自动释放向下增长空间较小内存映射段动态库、文件映射、共享内存高效 I/O 映射了解即可堆Heap动态申请的内存malloc/new手动申请手动释放向上增长空间大数据段静态区全局变量、静态变量static程序整个生命周期都存在程序结束释放代码段常量区可执行代码、只读常量字符串常量只读不可修改2. 变量位置判断结合以下的代码理解各个变量的存储位置。intglobalVar1;// 全局变量 → 数据段静态区staticintstaticGlobalVar1;// 全局静态变量 → 数据段静态区voidTest(){staticintstaticVar1;// 局部静态变量 → 数据段静态区intlocalVar1;// 局部变量 → 栈intnum1[10]{1,2,3,4};// 局部数组 → 栈charchar2[]abcd;// char2数组本身在栈字符串常量abcd在代码段数组拷贝了常量内容constchar*pChar3abcd;// 指针pChar3在栈指向的字符串abcd在代码段常量区int*ptr1(int*)malloc(sizeof(int)*4);// ptr1指针在栈指向的空间在堆int*ptr2(int*)calloc(4,sizeof(int));// ptr2指针在栈指向的空间在堆int*ptr3(int*)realloc(ptr2,sizeof(int)*4);// ptr3指针在栈指向的空间在堆free(ptr1);free(ptr3);}易错点1. 栈 vs 堆栈函数调用自动分配结束自动回收速度快空间小堆手动申请手动释放速度慢空间大使用灵活。2. 字符串常量abcd这种字符串字面量存在代码段常量区只读不可修改char char2[] abcd是把常量拷贝到栈上的数组里数组内容可以修改。3. static 变量不管是全局还是局部 static都存在数据段生命周期贯穿整个程序。二、C 语言动态内存管理malloc/calloc/reallocC 语言管理堆内存靠四个函数malloc、calloc、realloc、free。1. 函数对比函数作用初始化参数malloc申请指定字节数的堆空间不初始化内容随机总字节数calloc申请指定个数指定大小的空间自动初始化为 0个数 单个大小realloc对已有空间扩容不初始化新空间原指针 新总大小free释放堆空间-指向堆空间的指针知识点1realloc给已有空间扩容分两种情况原地扩容原空间后面有足够空位直接扩大返回原地址异地扩容原空间后面不够找一块更大的新空间拷贝数据释放旧空间返回新地址。知识点2realloc 后原来的指针需要 free 吗int*p2(int*)calloc(4,sizeof(int));int*p3(int*)realloc(p2,sizeof(int)*10);// 这里需要 free(p2) 吗不需要因为如果是原地扩容p3和p2地址一样free(p3)就释放了如果是异地扩容realloc内部会自动释放旧空间p2不用手动 free。三、C 内存管理new 和 deleteC 语言的 malloc/free 在 C 里依然能用但对付自定义类型就很麻烦 —— malloc只开空间不会调用构造函数free只释放空间不会调用析构函数。于是C 推出了自己的内存管理操作符new和delete。1. new/delete 操作内置类型对于int、char等内置类型new/delete和malloc/free功能差不多只是写法更简洁。voidTest(){// 1. 申请单个int空间不初始化int*ptr1newint;// 2. 申请单个int空间初始化为10int*ptr2newint(10);// 3. 申请连续10个int的数组int*ptr3newint[10];// 数组初始化C11支持列表初始化int*ptr4newint[5]{1,2,3,4,5};// 释放单个空间deleteptr1;deleteptr2;// 释放数组空间必须加 []delete[]ptr3;delete[]ptr4;}重要匹配规则new对应deletenew[]对应delete[]不匹配使用会导致内存泄漏甚至程序崩溃。2. new/delete 操作自定义类型这才是 new/delete 真正的价值申请空间 调用构造函数 / 释放空间前调用析构函数。#includeiostreamusingnamespacestd;classA{public:A(inta0):_a(a){coutA():thisendl;}~A(){cout~A():thisendl;}private:int_a;};intmain(){// C语言方式只开空间不调用构造A*p1(A*)malloc(sizeof(A));free(p1);// 只释放空间不调用析构// C方式开空间 调用构造函数A*p2newA(1);deletep2;// 先调用析构再释放空间cout----------------endl;// 数组版本A*p3newA[2];// 调用2次构造函数delete[]p3;// 调用2次析构函数return0;}核心结论malloc/free只负责开 / 释放空间不管对象的构造和析构new/deletenew先开空间再调用构造函数初始化对象delete先调用析构函数清理资源再释放空间。这也是为什么 C 推荐用new/delete管理自定义类型。四、底层揭秘operator new 与 operator deletenew不是直接调用malloc其实中间还有一层operator new 和 operator delete 全局函数。下面我们通过反汇编窗口查看operator new operator delete 还需要继续深入反汇编比较难理解这里只是让大家看一下底层确实是调用了operator new 和 operator delete 全局函数更多细节就不讨论了。1. operator new 是什么operator new 是 C 提供的全局函数不是运算符重载它的本质就是封装了 malloc 失败抛异常。可以把源码简单理解为void*operatornew(size_t size){void*p;// 循环调用malloc申请空间while((pmalloc(size))0){// 申请失败调用用户设置的应对措施if(_callnewh(size)0){// 没有应对措施抛出 bad_alloc 异常throwbad_alloc();}}returnp;}关键区别malloc 失败 VS new 失败malloc 失败返回NULL使用前必须判空new 失败抛出bad_alloc异常不需要判空需要捕获异常。2. operator delete 是什么operator delete 同样是全局函数底层封装了free专门用来释放operator new申请的空间。3. 关系链简单把关系捋一下就形成了new 操作符 → 调用 operator new 全局函数 → 底层调用 malloc 申请空间 → 申请成功返回地址失败抛异常delete 操作符 → 先调用析构函数 → 调用 operator delete 全局函数 → 底层调用 free 释放空间五、new 和 delete 的实现原理1. 内置类型对于int、double等内置类型new和malloc几乎一样只是语法不同、失败处理不同delete和free几乎一样new[]申请连续空间delete[]释放连续空间。2. 自定义类型new 的执行流程调用operator new函数申请堆空间在申请好的空间上执行构造函数完成对象初始化。delete 的执行流程在对象上执行析构函数清理对象内部资源调用operator delete函数释放堆空间。new T [N] 的执行流程调用operator new[]底层调用operator new申请N个对象的总空间在空间上执行N次构造函数逐个初始化每个对象。delete [] 的执行流程在空间上执行N次析构函数逐个清理每个对象调用operator delete[]底层调用operator delete释放总空间。这也解释了为什么new[]必须配delete[] 编译器要知道调用多少次析构函数不匹配会导致少调用析构或越界访问。总结欢迎点赞 收藏 关注