
目录一、C/C内存分布二、C内存管理方式1.new和delete操作内置类型2.new和delete操作自定义类型3.new 内存申请失败会抛出异常三、operator new与operator delete函数1.operator new2.operator delete四、new和delete的实现原理1.内置类型2.自定义类型五、定位new表达式placement-new)六、malloc/free和new/delete的区别一、C/C内存分布一个 C/C 程序运行时内存主要分为 5 个区域栈堆栈存放非静态局部变量、函数参数、函数返回值等栈内存向下增长内存映射段高效 I/O 映射方式主要用来加载共享动态库。用户可以通过系统接口创建共享内存用于进程间通信堆用于程序运行过程中动态内存分配堆内存向上增长是 new/malloc 主要操作的区域数据段存储全局数据、静态数据全局 static、局部 static 变量代码段存放可执行机器代码、只读常量二、C内存管理方式C 语言动态内存管理malloc / calloc / realloc / free。C 语言这套接口 C 可以继续使用但有些场景处理起来比较麻烦所以 C 提供自己的动态内存管理方式new和delete操作符1.new和delete操作内置类型#includeiostreamusingnamespacestd;intmain(){int*p1newint;// 申请单个int空间不初始化int*p2newint[10];// 申请10个int的数组空间deletep1;delete[]p2;//申请对象初始化int*p3newint(0);//单个对象 动态申请一个int类型的空间并初始化为0//多个对象int*p4newint[10]{0};int*p5newint[10]{1,5,3,4,6,2};return0;}Tips申请释放单个元素用new / delete申请释放连续数组空间用new[] / delete[]一定要匹配2.new和delete操作自定义类型对于自定义类型new 会自动调用构造函数delete 会自动调用析构函数malloc/free 只会开辟 / 释放空间不会调用构造、析构#includeiostreamusingnamespacestd;classA{public:A(){coutA()默认构造endl;}A(intx){coutA(int)带参构造endl;}~A(){cout~A()析构endl;}};intmain(){A*p1newA;// 调用默认构造函数A*p2newA(1);// 调用单参数带参构造函数deletep1;// 先调用析构再释放堆空间deletep2;return0;}申请空间对于自定义类型自动调用构造和析构函数无默认构造函数数组对象显式初始化的几种写法:#includeiostreamusingnamespacestd;classA{public:A(intx,inty):_x(x),_y(y){}private:int_x;int_y;};intmain(){// 已有有名对象数组元素调用拷贝构造Aaa1(1,1);Aaa2(2,2);Aaa3(3,3);A*p1newA[3]{aa1,aa2,aa3};// 调用拷贝构造// 传入匿名临时对象A*p2newA[3]{A(1,1),A(2,2),A(3,3)};// C11构造临时对象再拷贝构造数组元素编译器允许拷贝消除优化直接在数组空间构造// C17强制拷贝消除不会发生拷贝构造// 花括号列表初始化直接调用多参数构造函数A*p3newA[3]{{1,1},{2,2},{3,3}};delete[]p1;delete[]p2;delete[]p3;return0;}在申请自定义类型的空间时new会调用构造函数delete会调用析构函数而malloc与free不会3.new 内存申请失败会抛出异常new申请内存失败不会返回 NULL而是抛出bad_alloc异常需要用try‑catch捕获#includeiostream#includeexceptionusingnamespacestd;intmain(){try{void*p1newchar[1024*1024*1024];coutp1endl;void*p2newchar[1024*1024*1024];coutp2endl;}catch(constexceptione){coute.what()endl;}return0;}三、operator new与operator delete函数new / delete供用户使用完成动态内存申请和释放的操作符operator new / operator delete系统提供的全局函数new 底层调用operator new申请堆空间delete 底层调用operator delete释放堆空间。1.operator new底层本质调用mallocmalloc 成功直接返回内存地址malloc 失败执行用户注册的内存不足处理函数如果没有处理函数或者处理后依旧分配失败抛出bad_alloc异常void*__CRTDECLoperatornew(size_t size)_THROW1(_STD bad_alloc){void*p;while((pmalloc(size))0){if(_callnewh(size)0){// 申请内存失败抛出bad_alloc异常staticconststd::bad_alloc nomem;_RAISE(nomem);}}return(p);}2.operator delete底层本质调用free完成内存释放内部做了调试校验、线程加锁保护voidoperatordelete(void*pUserData){_CrtMemBlockHeader*pHead;RTCCALLBACK(_RTC_Free_hook,(pUserData,0));if(pUserDataNULL)return;_mlock(_HEAP_LOCK);/* 锁住堆防止多线程冲突 */__TRY pHeadpHdr(pUserData);_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead-nBlockUse));_free_dbg(pUserData,pHead-nBlockUse);__FINALLY_munlock(_HEAP_LOCK);/* 释放锁 */__END_TRY_FINALLYreturn;}// free本质就是 _free_dbg#definefree(p)_free_dbg(p,_NORMAL_BLOCK)总结operator new内部调用malloc申请空间成功直接返回失败抛异常operator delete内部调用free完成内存回收四、new和delete的实现原理1.内置类型对于内置类型new/malloc、delete/free行为大体相似区别new[] / delete[]用来申请释放数组new / delete处理单个对象new 分配失败抛异常malloc 失败返回NULL如果是内置类型调用不匹配intmain(){int*p1newint[10];deletep1;return0;}内置类型下new [] 和 delete 不匹配程序不一定崩溃、也不一定内存泄漏但这依然是标准定义的未定义行为不代表代码合法因为内置类型没有构造析构底层最终就是 malloc 和 free2.自定义类型new的原理调用operator new向堆申请原始空间在这块内存上调用构造函数完成对象初始化。delete的原理调用对象的析构函数清理对象内部资源调用operator delete释放堆内存。new T[N]的原理调用operator new[]内部再调用operator new申请 N 个对象的总空间在这块内存上循环执行N 次构造函数。delete[]的原理在数组对象上循环执行N 次析构函数调用operator delete[]内部调用operator delete释放整块堆内存数组 new [] 会额外保存对象个数当使用new[]创建自定义类型数组编译器会多开辟一小块空间存储对象的数量delete[]依靠这个数字知道要调用多少次析构。如果类没有显式析构编译器会做优化不再额外保存计数自定义类型不匹配#includeiostreamusingnamespacestd;// 类B没有显式写析构classB{private:int_b11;int_b22;};// 类A显式提供析构classA{public:~A(){cout~A()调用endl;}private:int_a11;int_a22;};intmain(){// Bnew[]搭配deleteVS下不会崩溃B*p2newB[10];deletep2;// Anew[]搭配delete运行直接崩溃A*p3newA[10];deletep3;return0;}当开多个对象的数组时会在数组头上多开4个字节存储对象个数严格来说这里A和B都应该在头上开4个字节但是这里B没有开因为编译器看到B没有写析构函数所以这里自动优化了这里多开的四个字节是给[]用的用于了解有几个对象要调用几次析构函数这里B没有崩A崩了就是因为A是从四个字节之后开始析构的Tips一定要保证new -- deletenew[] -- delete[]严格匹配五、定位new表达式placement-new)定位new表达式在已分配的原始内存空间中调用构造函数初始化一个对象使用格式new (place_address) type或者new (place_address) type(initializer-list)place_address必须是一个指针表明要对哪个指针指向的空间调用构造initializer-list是类型的初始化列表使用场景多用于内存池。内存池只分配原始裸内存不会自动调用构造拿到裸内存后使用定位 new 显式调用构造完成自定义对象初始化注意构造函数不能直接手动调用析构函数可以显式手动调用#includeiostreamusingnamespacestd;classA{public:A(inta0):_a(a){coutA():thisendl;}~A(){cout~A():thisendl;}private:int_a;};intmain(){//构造A*p1newA(1);//1.A*p2(A*)operatornew(sizeof(A);//2.//区别是1.初始化了2.没有初始化只开了空间new(p2)A(1);//用定位new对已开好的空间显式初始化//析构deletep1;p2-~A();//析构函数可以直接显式调用构造函数不行operatordelete(p2);return0;]六、malloc/free和new/delete的区别共同点都是在堆上申请内存都需要手动释放内存不同点malloc / free是库函数new / delete是 C操作符。malloc申请出来的内存不会初始化new支持申请同时完成对象初始化。malloc需要手动传入字节大小new直接写类型编译器自动计算大小数组只需填写对象个数。malloc返回void*使用必须强制类型转换new返回对应类型指针不需要强转。malloc分配失败返回NULL需要手动判空new分配失败抛出bad_alloc异常需要try‑catch捕获。自定义类型malloc/free只负责开辟释放空间不会调用构造、析构函数new申请内存后调用构造函数delete释放内存前调用析构函数清理资源