目录
- 一、C/C++内存分布
- 二、C++内存管理方式
- 1.new和delete操作内置类型
- 2.new和delete操作自定义类型
- 3.new 内存申请失败会抛出异常
- 三、operator new与operator delete函数
- 1.operator new
- 2.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操作内置类型
#include<iostream>usingnamespacestd;intmain(){int*p1=newint;// 申请单个int空间,不初始化int*p2=newint[10];// 申请10个int的数组空间deletep1;delete[]p2;//申请对象+初始化int*p3=newint(0);//单个对象 动态申请一个int类型的空间并初始化为0//多个对象int*p4=newint[10]{0};int*p5=newint[10]{1,5,3,4,6,2};return0;}Tips:申请释放单个元素用
new / delete;申请释放连续数组空间用new[] / delete[],一定要匹配
2.new和delete操作自定义类型
对于自定义类型:new 会自动调用构造函数,delete 会自动调用析构函数;malloc/free 只会开辟 / 释放空间,不会调用构造、析构
#include<iostream>usingnamespacestd;classA{public:A(){cout<<"A()默认构造"<<endl;}A(intx){cout<<"A(int)带参构造"<<endl;}~A(){cout<<"~A()析构"<<endl;}};intmain(){A*p1=newA;// 调用默认构造函数A*p2=newA(1);// 调用单参数带参构造函数deletep1;// 先调用析构,再释放堆空间deletep2;return0;}申请空间,对于自定义类型自动调用构造和析构函数
无默认构造函数,数组对象显式初始化的几种写法:
#include<iostream>usingnamespacestd;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*p1=newA[3]{aa1,aa2,aa3};// 调用拷贝构造// 传入匿名临时对象A*p2=newA[3]{A(1,1),A(2,2),A(3,3)};// C++11:构造临时对象,再拷贝构造数组元素;编译器允许拷贝消除优化,直接在数组空间构造// C++17:强制拷贝消除,不会发生拷贝构造// 花括号列表初始化,直接调用多参数构造函数A*p3=newA[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捕获
#include<iostream>#include<exception>usingnamespacestd;intmain(){try{void*p1=newchar[1024*1024*1024];cout<<p1<<endl;void*p2=newchar[1024*1024*1024];cout<<p2<<endl;}catch(constexception&e){cout<<e.what()<<endl;}return0;}三、operator new与operator delete函数
new / delete:供用户使用完成动态内存申请和释放的操作符operator new / operator delete:系统提供的全局函数
new 底层调用
operator new申请堆空间;delete 底层调用operator delete释放堆空间。
1.operator new
底层本质调用malloc:
- malloc 成功,直接返回内存地址;
- malloc 失败,执行用户注册的内存不足处理函数;
- 如果没有处理函数或者处理后依旧分配失败,抛出
bad_alloc异常
void*__CRTDECLoperatornew(size_t size)_THROW1(_STD bad_alloc){void*p;while((p=malloc(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(pUserData==NULL)return;_mlock(_HEAP_LOCK);/* 锁住堆,防止多线程冲突 */__TRY pHead=pHdr(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*p1=newint[10];deletep1;return0;}内置类型下,new [] 和 delete 不匹配,程序不一定崩溃、也不一定内存泄漏,但这依然是标准定义的未定义行为,不代表代码合法
因为内置类型没有构造析构,底层最终就是 malloc 和 free
2.自定义类型
new的原理:
- 调用
operator new向堆申请原始空间; - 在这块内存上调用构造函数,完成对象初始化。
delete的原理:
- 调用对象的析构函数,清理对象内部资源;
- 调用
operator delete释放堆内存。
new T[N]的原理:
- 调用
operator new[],内部再调用operator new,申请 N 个对象的总空间; - 在这块内存上循环执行N 次构造函数。
delete[]的原理:
- 在数组对象上循环执行N 次析构函数;
- 调用
operator delete[],内部调用operator delete释放整块堆内存
数组 new [] 会额外保存对象个数:
当使用new[]创建自定义类型数组,编译器会多开辟一小块空间存储对象的数量,delete[]依靠这个数字知道要调用多少次析构。
如果类没有显式析构,编译器会做优化,不再额外保存计数
自定义类型不匹配:
#include<iostream>usingnamespacestd;// 类B:没有显式写析构classB{private:int_b1=1;int_b2=2;};// 类A:显式提供析构classA{public:~A(){cout<<"~A()调用"<<endl;}private:int_a1=1;int_a2=2;};intmain(){// B:new[]搭配delete,VS下不会崩溃B*p2=newB[10];deletep2;// A:new[]搭配delete,运行直接崩溃A*p3=newA[10];deletep3;return0;}当开多个对象的数组时,会在数组头上多开4个字节存储对象个数
严格来说这里A和B都应该在头上开4个字节,但是这里B没有开,因为编译器看到B没有写析构函数,所以这里自动优化了
这里多开的四个字节是给[]用的,用于了解有几个对象要调用几次析构函数
这里B没有崩,A崩了,就是因为A是从四个字节之后开始析构的
Tips:一定要保证new <--> delete,new[] <--> delete[]严格匹配
五、定位new表达式(placement-new)
定位new表达式:在已分配的原始内存空间中调用构造函数初始化一个对象
使用格式:new (place_address) type或者new (place_address) type(initializer-list)
place_address:必须是一个指针,表明要对哪个指针指向的空间调用构造
initializer-list:是类型的初始化列表
使用场景:
多用于内存池。内存池只分配原始裸内存,不会自动调用构造;拿到裸内存后,使用定位 new 显式调用构造完成自定义对象初始化
注意:构造函数不能直接手动调用;析构函数可以显式手动调用
#include<iostream>usingnamespacestd;classA{public:A(inta=0):_a(a){cout<<"A():"<<this<<endl;}~A(){cout<<"~A():"<<this<<endl;}private:int_a;};intmain(){//构造A*p1=newA(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释放内存前调用析构函数清理资源