第四站:C++内存管理 📅 2026/7/1 1:52:12 C/C内存分布在程序运行时的五大内存区域1栈区内容 函数的形参 函数内的局部变量 函数调用的返回地址 特点 自动分配、自动释放无需手动管理 遵循后进先出LIFO 函数执行完栈内存自动回收2堆区内容手动申请的内存 申请方式Cmalloc/calloc/reallocCnew释放方式Cfree()Cdelete 特点空间大手动管理不释放会造成内存泄漏 生命周期手动申请 → 手动释放或程序结束3数据段全局静态内容 全局变量static修饰的静态变量全局/局部静态 特点 自动初始化为0或 空值程序结束由系统自动释放全局共享整个程序都能访问例如intg_a10;// 全局变量 → 全局区已初始化staticints_b20;// 静态变量 → 全局区voidfunc(){staticints_c30;// 局部静态 → 仍在全局区}4常量区存放内容字符串常量、const修饰的全局常量 特点只读修改会直接程序崩溃相同常量只存一份节约内存 生命周期整个程序运行期间例如constchar*strHello World;// Hello World 存放在常量区// str 是指针变量存在栈区5代码区内容编译后的二进制机器指令函数体代码 特点只读防止程序被意外修改共享多次运行同一个程序共用一份代码 生命周期整个程序运行期间都存在指针的大小如何计算C/C 中指针变量的大小只和操作系统位数有关和它指向什么类型无关。原因是指针存放的是内存地址不管指向声明类型地址本身的长度是固定的。系统所有指针(int*,char*,void*…)大小32位系统4字节64位系统8字节注意1数组名≠指针只有当数组名作为函数参数时才会退化为指针。2strlen 必须保证指针指向的字符串有 \0 结束否则会越界访问结果不可控相关小知识int**heap_ptr(int**)malloc(sizeof(int*));分析 ·int*:整形指针int**:二级指针 ·sizeof(int*)表示一个一级指针的大小32位系统是464位系统为8·(int**)强制类型转换malloc 返回void*强转为int** 意思是把这个堆地址当成存放一级指针(int*)的地址来使用 ·heap_ptr 是二级指针变量全局普通变量【全局定义在所有函数外面】与全局静态变量有什么区别全局普通变量在所有文件中可见其他文件可以用extern变量类型变量名就能直接引用全局静态变量只在当前源文件可见注意在同一个工程项目中如果两个源文件都定义全局普通变量int a;会直接报错如下图所示在两个完全不同的工程项目中就不会影响但全局静态变量就没问题变量的创建与析构顺序using namespace std;//创建四个类用于查看变量创建顺序与析构顺序classA{public:A(){coutAendl;}~A(){cout~Aendl;}private:int_a;};classB{public:B(){coutBendl;}~B(){cout~Bendl;}private:int_b;};classC{public:C(){coutCendl;}~C(){cout~Cendl;}private:int_c;};classD{public:D(){coutDendl;}~D(){cout~Dendl;}private:int_d;};//全局变量Cc;intmain(){Aa;Bb;staticDd;return0;}//查看变量创建顺序与析构顺序。注意局部静态变量在函数结束时最后析构。计算拷贝构造的次数classWidget{/* ... */};// 假设已实现拷贝构造函数Widgetf(Widgetu)//拷贝次数为1 //拷贝次数为5{Widgetv(u);//拷贝次数为2 //拷贝次数为6 // 拷贝构造 vWidgetwv;//拷贝次数为3 //拷贝次数为7 // 拷贝构造 wreturnw;//拷贝次数为4 //拷贝次数为8 // 返回值优化相关}intmain(){Widgetx;// 默认构造Widgetyf(f(x));//拷贝次数为9 // 嵌套调用 拷贝构造}解析考虑系统优化时优化时机仅发生在函数传参和函数返回这两个环节。拷贝次数为4和拷贝次数为5合并拷贝次数为8和拷贝次数为9合并所有共7次。c和c中内存管理方式与区别malloc与new的区别malloc只申请空间new包含申请空间构造函数初始化释放空间时free只释放空间delete包含释放空间还调用了析构函数#includeiostreamusing namespace std;//C和c中内存管理方式//int main() {// //C函数// int* p1 (int*)malloc (sizeof(int));// int* p2 (int*)malloc(sizeof(int) * 10);// //释放// free(p1);// free(p2);// //c操作符//// int* p3 new int;//开辟一个空间// int* p5 new int(10);//开辟一个空间初始化为10// int* p4 new int[10];//开辟10个空间//int* p new int[3](1);//不可以写成这样编译器直接报错了// //释放// delete p5;// delete[] p4;// return 0;//}//malloc与new的区别malloc只申请空间new包含申请空间构造函数初始化//释放空间时free只释放空间delete包含释放空间还调用了析构函数classA{public:A(inta0):_a(0){_aa;coutAendl;}~A(){cout~Aendl;}private:int_a;};intmain(){int*p1newint;int*p2(int*)malloc(sizeof(int));A*p3(A*)malloc(sizeof(A));A*p4newA;//会输出A//释放free(p3);delete p4;//会输出~Areturn0;}operator new与operator delete函数申请资源1operator new 底层调用 malloc失败抛出异常bad allocation2new operator new 构造函数3new 比起malloc不一样的地方1.调用构造函数初始化2.失败了抛出异常4delete与free不一样的地方delete会调用析构函数清理资源5operator delete和free作用相似都是只释放原始内存不调用析构但不是完全等同operator delete 是 C 标准库函数可以被重载free 是 C 库函数不能重载。底层默认 operator delete 内部确实调用 free但语法、可扩展性不一样不能说完全没区别。举例classA{public://构造函数A(constinta0):_a(a){coutA()endl;}~A(){cout~A()endl;}private:int_a;};intmain(){A*p1(A*)malloc(sizeof(A));//只分配内存不调用构造函数控制台 不会输出 A()//A* p2 new A;A*p3(A*)operatornew(sizeof(A));//底层调用 malloc 分配内存只分配内存不调用构造函数//失败会抛异常这里大小正常不会抛异常//为什么会出现下面的结果这是一个值得思考的问题void*p4malloc(1024*1024*1024*1);//0166B040coutp4endl;void*p5malloc(1024*1024*1024*2);//00000000coutp5endl;void*p6malloc(1024*1024*1024*3);//00000000coutp6endl;void*p7malloc(1024*1024*1024*4);//012F1900coutp7endl;//这里不是真的一次性拿到 4GB 物理内存//而是malloc 只分配虚拟地址页表不立刻分配物理内存size_t size2;void*p8malloc(size*1024*1024*1024);//2Gcoutp8endl;//00000000失败返回0//void* p9 operator new(size * 1024 * 1024 * 1024);//cout p9 endl;//失败抛出异常try{void*p9operatornew(size*1024*1024*1024);coutp9endl;}catch(exceptione){coute.what()endl;//bad allocation}return0;}operator new 和malloc的区别是什么结果使用方式都是一样的处理错误的方式不一样malloc分配内存不足时返回空指针不触发异常需要手动去判空看内存是否分配成功operator new失败时会直接抛出std::bad_alloc,不返回空指针如果不写 try-catch 捕获程序直接崩溃终止用operator new申请空间我们想要调用构造函数时的调用方法//使用operator new申请空间我们想要调用构造函数时的调用方法//typedef unsinged int size_t;classA{public://构造函数A(constinta0):_a(a){coutA()endl;}~A(){cout~A()endl;}private:int_a;};intmain(){//1. 分配原始堆内存不调用构造A*p1(A*)operatornew(sizeof(A));//在已有的内存p1上手动构造A对象new(p1)A(10);//编译器会自动拆分成两步//调用 operator new(sizeof(A), p1)返回 p1 本身//在返回的地址上调用构造函数 A::A(10)完成对象初始化。// 销毁对象p1-~A();// 释放内存原来怎么分配就怎么释放operatordelete(p1);return0;}注意new /operator new /malloc 最终都是去 堆 (heap) 上申请内存malloc 和 new 的区别1.初始化与构造new 会调用构造函数完成对象初始化malloc仅分配内存不会调用构造函数。2.失败处理:new 内存分配失败时抛出异常malloc 失败时返回 03.本质不同:malloc 是 C 标准库提供的库函数new 是 C 内置操作符关键字。4.使用方式malloc 需要手动指定分配的字节数返回值是无类型的 void*必须强转才能使用;new 后面直接跟数据类型 / 对象无需计算字节数返回值是对应类型的指针无需强转。5.配套释放new 分配的内存必须用delete释放会调用析构函数malloc 分配的内存必须用free释放。内存泄漏定义程序中已经不再使用的动态分配内存因为忘记释放、释放逻辑错误等原因导致内存一直被占用就是内存泄漏。c/c与Java的区别内存泄漏是所有手动管理内存的语言C/C 都存在的问题不是 C 独有的。Java 等语言拥有自动垃圾回收机制GC对象不再被引用时会自动回收内存不需要手动释放几乎不会出现内存泄漏。内存泄漏的危害长期运行的服务 / 程序如服务器、后台程序泄漏会导致可用内存越来越少最终程序运行变慢、卡顿甚至崩溃。内存资源有限的设备嵌入式、单片机、移动端少量泄漏也会快速耗尽内存导致程序异常。