行业资讯
📅 2026/7/16 4:29:41
【C++】 内存管理
目录1. C/C内存分布2. C语言中动态内存管理方式3. C中动态内存管理方式3.1 new / delete 操作内置类型3.2 new 和 delete 操作自定义类型4. operator new 与 operator delete 函数5. new 和 delete 的实现原理5.1 内置类型5.2 自定义类型6. 定位 new 表达式 (placement-new)7. 常见面试题7.1 malloc/free 和 new/delete 的区别7.2 内存泄漏1. C/C内存分布我们先来看下面的一段代码和相关问题intglobalVar1;staticintstaticGlobalVar1;voidTest(){staticintstaticVar1;intlocalVar1;intnum1[10]{1,2,3,4};charchar2[]abcd;constchar*pChar3abcd;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);}1. 选择题选项A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)globalVar在哪里staticGlobalVarstaticVarlocalVarnum1char2*char2pChar3*pChar3ptr1*ptr12. 填空题sizeof(num1) sizeof(char2) sizeof(pChar3) sizeof(ptr1) 3. sizeof 和 strlen 区别【内存区域划分说明】栈堆栈存储非静态局部变量、函数参数、返回值等向下增长。内存映射段高效的I/O映射方式用于装载共享的动态内存库。用户可使用系统接口创建共享内存进行进程间通信。堆用于程序运行时动态内存分配向上增长。数据段存储全局数据和静态数据。代码段存储可执行的代码或只读常量。2. C语言中动态内存管理方式管理方式malloc/calloc/realloc/freevoidTest(){int*p1(int*)malloc(sizeof(int));free(p1);// 1. malloc/calloc/realloc的区别是什么int*p2(int*)calloc(4,sizeof(int));int*p3(int*)realloc(p2,sizeof(int)*10);// 这里需要free(p2)吗free(p3);}【面试题】​malloc/calloc/realloc的区别1malloc函数。其原型void *malloc(unsigned int num_bytes)num_byte为要申请的空间大小需要我们手动的去计算如int p (int )malloc(20*sizeof(int)),如果编译器默认int为4字节存储的话那么计算结果是80Byte一次申请一个80Byte的连续空间并将空间基地址强制转换为int类型赋值给指针p,此时申请的内存值是不确定的。2calloc函数其原型void *calloc(size_t n, size_t size)其比malloc函数多一个参数并不需要人为的计算空间的大小比如如果他要申请20个int类型空间会int *p (int *)calloc(20, sizeof(int),这样就省去了人为空间计算的麻烦。但这并不是他们之间最重要的区别malloc申请后空间的值是随机的并没有进行初始化而calloc却在申请后对空间逐一进行初始化并设置值为0;3realloc函数和上面两个有本质的区别其原型void realloc(void *ptr, size_t new_Size)用于对动态内存进行扩容(及已申请的动态空间不够使用需要进行空间扩容操作)ptr为指向原来空间基址的指针 new_size为接下来需要扩充容量的大小。3. C中动态内存管理方式C语言的内存管理方式在C中依然适用但在处理某些情况如自定义类型初始化时较为繁琐。因此C提出了通过new和delete操作符进行动态内存管理的专属方式。3.1 new / delete 操作内置类型voidTest(){// 动态申请一个int类型的空间int*ptr4newint;// 动态申请一个int类型的空间并初始化为10(注:原课件也有3的图示)int*ptr5newint(10);// 动态申请3个int类型的空间(数组)int*ptr6newint[3];deleteptr4;deleteptr5;delete[]ptr6;}注意申请和释放单个元素的空间使用new和delete申请和释放连续的空间使用new[]和delete[]。务必匹配使用。3.2 new 和 delete 操作自定义类型classA{public:A(inta0):_a(a){coutA():thisendl;}~A(){cout~A():thisendl;}private:int_a;};intmain(){// new/delete 和 malloc/free 最大区别是对于【自定义类型】new/delete 除了开空间还会调用构造函数和析构函数A*p1(A*)malloc(sizeof(A));A*p2newA(1);free(p1);deletep2;// 内置类型几乎是一样的int*p3(int*)malloc(sizeof(int));int*p4newint;free(p3);deletep4;A*p5(A*)malloc(sizeof(A)*10);A*p6newA[10];free(p5);delete[]p6;return0;}注意申请自定义类型空间时new会调用构造函数delete会调用析构函数而malloc与free不会。4. operator new 与 operator delete 函数new和delete是用户态的操作符而operator new和operator delete是系统提供的全局函数。底层逻辑new调用operator new申请空间delete调用operator delete释放空间。分为两步1.分配内存2.调用A()构造对象。事实上分配内存这一操作就是由operator new(size_t)来完成的如果类A重载了operator new那么将调用A::operator new(size_t )如果没有重载就调用::operator new(size_t )全局new操作符由C默认提供。因此前面的两步也就是1.调用operator new 2.调用构造函数。new : 不能被重载其行为总是一致的。它先调用operator new分配内存然后调用构造函数初始化内存。new 操作符的执行过程调用operator new分配内存 调用构造函数生成类对象返回相应指针。operator new**要实现不同的内存分配行为应该重载operator new**而不是new;operator new就像operator 一样是可以重载的。​ 同理operator new[]、operator delete、operator delete[]也是可以重载的。operator new实际也是通过malloc来申请空间如果malloc申请空间成功就直接返回否则执行用户提供的空间不足应对措施如果提供该措施就继续申请否则就抛异常。operator delete 最终是通过free来释放空间的。operator new 源码实现机制实际通过malloc申请空间。成功则直接返回失败则尝试执行空间不足的应对措施若配置了应对措施则继续申请否则抛出bad_alloc异常。void*__CRTDECLoperatornew(size_t size)_THROW1(_STD bad_alloc){// try to allocate size bytesvoid*p;while((pmalloc(size))0){if(_callnewh(size)0){// report no memory// 如果申请内存失败了这里会抛出 bad_alloc 类型异常staticconststd::bad_alloc nomem;_RAISE(nomem);}}return(p);}operator delete 源码实现机制最终通过free释放空间。voidoperatordelete(void*pUserData){_CrtMemBlockHeader*pHead;RTCCALLBACK(_RTC_Free_hook,(pUserData,0));if(pUserDataNULL)return;_mlock(_HEAP_LOCK);/* block other threads */__TRY/* get a pointer to memory block header */pHeadpHdr(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_FINALLYreturn;}// free的实现#definefree(p)_free_dbg(p,_NORMAL_BLOCK)5. new 和 delete 的实现原理5.1 内置类型针对内置类型new/delete和malloc/free基本类似。区别在于new/delete申请和释放的是单个元素空间new[]/delete[]申请连续空间new失败抛异常malloc失败返回NULL。5.2 自定义类型new 的原理调用operator new申请空间。在申请的空间上执行构造函数完成对象构造。delete 的原理在空间上执行析构函数完成资源清理。调用operator delete释放对象的空间。new T[N] 的原理调用operator new[]函数实际在其中调用operator new完成N个对象空间的申请。在申请的空间上执行 N 次构造函数。delete[] 的原理在释放的对象空间上执行 N 次析构函数完成资源清理。调用operator delete[]释放空间实际在其内部调用operator delete。intmain(){//new底层实现原理A*p1(A*)operatornew(sizeof(A));//p1-A();//不支持这种显示构造new(p1)A;//对已有空间显示调用构造new(p1)A(10);//后加(10)是初始化//delete底层实现原理p1-~A();operatordelete(p1);//new[]底层实现原理A*p2(A*)operatornew(sizeof(A)*10);new(p2)A[10]{1,2,3,4};//后加[10]开10个空间{}是初始化for(inti0;i10;i){new(p2i)A(i);}//delete[]底层实现原理for(inti0;i10;i){(p2i)-~A();}operatordelete[](p2);return0;}malloc/free和new/delete的区别要匹配使用6. 定位 new 表达式 (placement-new)概念在已分配的原始内存空间中调用构造函数初始化一个对象。语法new (place_address) type或new (place_address) type(initializer-list)(注place_address必须是一个指针initializer-list是类型的初始化列表)使用场景一般配合内存池使用。由于内存池分配的内存未初始化对于自定义类型的对象需使用定位 new 显式调用构造函数进行初始化。classA{public:A(inta0):_a(a){coutA():thisendl;}~A(){cout~A():thisendl;}private:int_a;};// 定位new/replacement newintmain(){// p1现在指向的只不过是与A对象相同大小的一段空间还不能算是一个对象因为构造函数没有执行A*p1(A*)malloc(sizeof(A));new(p1)A;// 注意如果A类的构造函数有参数时此处需要传参p1-~A();free(p1);A*p2(A*)operatornew(sizeof(A));new(p2)A(10);p2-~A();operatordelete(p2);return0;}7. 常见面试题7.1 malloc/free 和 new/delete 的区别共同点均从堆上动态申请空间并且需要用户手动释放。不同点malloc/free是函数new/delete是操作符。malloc申请的空间不会初始化new可以初始化。malloc申请时需手动计算空间大小并传递new只需跟上空间类型即可多对象需指定个数。malloc的返回值为void*使用时必须强转new不需要。malloc申请失败返回NULL使用时必须判空new失败抛异常需要捕获。申请自定义类型对象时malloc/free只开辟空间不调用构造和析构函数new在申请空间后调用构造函数初始化delete释放前调用析构函数清理资源。7.2 内存泄漏7.2.1 什么是内存泄漏危害是什么什么是内存泄漏因疏忽或错误造成程序未能释放已经不再使用的内存。内存并未物理消失而是因设计错误失去了对该段内存的控制造成浪费。危害长期运行的程序出现内存泄漏影响极大如操作系统、后台服务等会导致响应越来越慢最终卡死。voidMemoryLeaks(){// 1. 内存申请了忘记释放int*p1(int*)malloc(sizeof(int));int*p2newint;// 2. 异常安全问题int*p3newint[10];Func();// 这里Func函数抛异常导致 delete[] p3 未执行p3没被释放delete[]p3;}7.2.2 内存泄漏分类堆内存泄漏 (Heap leak)通过malloc/new等从堆中分配的内存用完后未释放导致空间无法再被使用。系统资源泄漏程序使用的系统资源如套接字、文件描述符、管道等未释放严重可导致系统执行不稳定。7.2.3 如何检测内存泄漏在VS下可以使用系统提供的_CrtDumpMemoryLeaks()函数进行简单检测仅报出泄漏字节数无准确位置信息。intmain(){int*pnewint[10];// 将该函数放在main函数之后每次程序退出的时候就会检测是否存在内存泄漏_CrtDumpMemoryLeaks();return0;}// 程序退出后在输出窗口中可以检测到泄漏了多少字节// Detected memory leaks!// Dumping objects -// {79} normal block at 0x00EC5FB8, 40 bytes long.// Data: CD CD CD CD CD CD ...// Object dump complete.注如果是大型工程通常需借助第三方内存泄漏检测工具如Linux下的Valgrind或Windows下的VLD工具。7.2.4 如何避免内存泄漏事前预防养成良好的编码规范申请的内存空间记着匹配地释放理想状态遇异常时仍防不胜防。事前预防采用RAII 思想或智能指针来管理资源。事后查错部分公司规范使用内部私有内存管理库自带泄漏检测选项。事后查错出问题时使用第三方内存泄漏检测工具进行检测排查。