经过了两篇的类和对象分析,我们这一篇再次加餐,对new和malloc的分析,malloc的源码不在这篇介绍,会放到linux篇的内存池专题,所以我们这篇只要分析new。
这篇的主要目的就是,对象是怎么new出来的,以后别人问程序员没对象的时候,就可以理直气壮的说new出来。
C++学习,b站直播视频
文章目录
- 8.1 malloc & free
- 8.1.1 申请一个内存
- 8.1.2 malloc申请0字节
- 8.1.3 申请一个数组
- 8.2 new简单使用
- 8.2.1 new的简单使用
- 8.2.2 new源码
- 8.2.3 new一个对象
- 8.2.4 new一个简单类型数组
- 8.2.5 new一个类类型数组
- 5.2.6 总结new干了啥
- 8.2.7 new失败处理
- 8.3 delete
- 8.3.1 delete简单使用
- 8.3.2 delete一个类对象
- 8.3.3 delete简单类型数组
- 8.3.4 delete类类型数组
- 8.3.5 总结delete干了啥
- 8.4 new高级玩法
- 8.4.1 重载new
- 8.4.1.1 重载类中的new
- 8.4.1.2 重载类中的delete
- 8.4.1.3 重载全局的new
- 8.4.1.4 重载全局的delete
- 8.4.2 定位new
- 8.4.2.1 使用
- 8.4.2.2 重载定位new
- 8.4.3 多种new
- 8.4.5 内存池
8.1 malloc & free
首先我们来看看我们的老朋友,malloc和free,相信学过c语言的都知道malloc是申请内存的,那malloc是怎么申请内存的呢?我们这一篇不细讲,留到linux的内存池专题。我们目前只要把malloc看作一个内存池,我们调用了malloc函数,就是在这块内存池中申请内存,用完了就可以free释放,我们来看看怎么使用吧。
8.1.1 申请一个内存
// 8.1.1 malloc申请一个内存
// 直接malloc申请 一个int大小的内存
int* pint = (int*)malloc(sizeof(int));
if (pint == nullptr) // 判断内存是否申请失败
{
return 0;
}
*pint = 12; // 申请了内存就可以修改了
cout << *pint << " " << pint << "_msize " << _msize(pint) << " sizeof(*pint)" << sizeof(*pint) << endl; // 可以打印一些地址,到linux内存分析的时候,大家就对地址很熟悉了
std::cout << "Hello World!\n";
free(pint); // 最后记得free释放内存,使用malloc申请的内存,需要程序员自己来管理
pint = nullptr; // 这个可以置空,因为free函数内部不会置空,pint的值还是原来的,如果使用了,可能会导致其他变量的内存出现问题
上面就是malloc申请一个简单内存的写法。
8.1.2 malloc申请0字节
一般我们写代码不会有这种情况,有这情况可能都是面试,不过我们这次没有分析malloc源码,所以也不过多解释,malloc是可以申请0字节大小的内存的,并且返回也是成功的。
int* pZore = (int*)malloc(0);
printf("pZore = %p %d sizeof(*pZore)[%d]\n", pZore, _msize(pZore), sizeof(*pZore));
直接有这么个东西吧,后面我们再详细分析源码。
8.1.3 申请一个数组
上面是申请一个变量,接着我们申请一个数组看看是怎么写的。
// 8.1.2 malloc申请一个数组
int* pArryInt = (int*)malloc(sizeof(int) * 10); // 10个int
if (pArryInt == nullptr)
{
return -1;
}
// 使用数组
pArryInt[0] = 12345;
pArryInt[2] = 4455;
// 如果使用越界,会咋样?
//pArryInt[10] = 234; // 会报错,但是不能这么使用
// 释放的时候,也是传一个指针就可以了
free(pArryInt);
// 如果想了解,为啥free只传一个指针,还有malloc的底层,linux课程记得来,哈哈哈
跟申请单个差不多,只是在malloc的时候传的内存总大小,要乘以个数。
返回的这个指针是指向一长块内存的,所以通过指针++,或者数组来操作。
8.2 new简单使用
因为考虑后面,会来一个内存池专题,所以malloc就讲那么多。接着我们来看下new。
我们在c语言的时候,使用的都是malloc,但是在c++中,我们使用的都是new,**所以学了new以后,请忘记malloc。**可以开始拥抱c++的new。
8.2.1 new的简单使用
// 8.2.1 new的简单使用
int* pNewInt = new int(1); // 比较方便,不用传大小,其实int就是大小了
cout << *pNewInt << endl;
// 一个小细节
int* pNewInt2 = new int; // 这种没有括号会咋样
cout << *pNewInt2 << endl; // 输出pNewInt2,有括号的是初始化了,没有就不初始化
// 既然内存已经申请了,最后要释放掉
delete pNewInt;
delete pNewInt2;
是不是很简单,本来打算就这么介绍了,但是不小心gdb了一下,竟然然可以看到new的源码。
那我们就来看看。
8.2.2 new源码
本来是不打算介绍new源码的,不过gdb看了一下,发现并不难,那就看一下,哈哈哈。
150: int* pNewInt = new int(1); // 比较方便,不用传大小,其实int就是大小了
00007FF64F1C285B B9 04 00 00 00 mov ecx,4
// 这个是给new准备的大小,int是4字节
00007FF64F1C2860 E8 E6 E7 FF FF call operator new (07FF64F1C104Bh)
// 这个是调用operator new 等会介绍这个
00007FF64F1C2865 48 89 85 88 03 00 00 mov qword ptr [rbp+388h],rax
// 这个我们就假设成功调用了operator new返回了值,就在rax中
00007FF64F1C286C 48 83 BD 88 03 00 00 00 cmp qword ptr [rbp+388h],0
// 编译器判断这个new是否等于0
00007FF64F1C2874 74 1D je main+53h (07FF64F1C2893h)
// 如果等于0,就跳转 goto ret0
// 如果不等于0,就把初始化值1,写进来
00007FF64F1C2876 48 8B 85 88 03 00 00 mov rax,qword ptr [rbp+388h]
00007FF64F1C287D C7 00 01 00 00 00 mov dword ptr [rax],1
00007FF64F1C2883 48 8B 85 88 03 00 00 mov rax,qword ptr [rbp+388h]
00007FF64F1C288A 48 89 85 78 06 00 00 mov qword ptr [rbp+678h],rax
// 反正就是把1赋值过来,然后跳转
00007FF64F1C2891 EB 0B jmp main+5Eh (07FF64F1C289Eh)
// 如果是0就跳转到这个位置,并且把rbp+678h这个内存清0
00007FF64F1C2893 48 C7 85 78 06 00 00 00 00 00 00 mov qword ptr [rbp+678h],0
// 不管那个,都会执行这个,就会把这个值赋值到我们指针
00007FF64F1C289E 48 8B 85 78 06 00 00 mov rax,qword ptr [rbp+678h]
00007FF64F1C28A5 48 89 45 08 mov qword ptr [pNewInt],rax
这个是调用了new这个关键字的汇编代码。
看起来就是为了调用这个operator new的。
我们接着来看operator new的代码
// C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\new_scalar.cpp
// 上面就是new代码的路径,是通过gdb找过来的,要不然还真不知道在哪
_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
for (;;)
{
// 这个就是new的核心代码,调用了malloc函数,这里是不是想吐槽了,哈哈哈,
// 一直觉得new是多么的高大上
if (void* const block = malloc(size))
{
return block;
}
// 后面就是如果malloc失败了,就会抛异常,这个我们后面来探讨
if (_callnewh(size) == 0)
{
if (size == SIZE_MAX)
{
__scrt_throw_std_bad_array_new_length();
}
else
{
__scrt_throw_std_bad_alloc();
}
}
// The new handler was successful; try to allocate again...
}
}
看了一下,这个new源码是不是如此简单,malloc起码还要搞一个内存池,这个new就离谱,只是调用了一个malloc,看了代码才发现,离了个大普。
8.2.3 new一个对象
上面是new一个简单的int类型变量,那我们new的一个对象呢?我们new一个对象的时候,编译器到底帮我们做了啥?对象又是怎么new出来的?
我们来看看:
162: // 8.2.3 new一个对象
163: A* pA = new A();
00007FF694A82997 B9 01 00 00 00 mov ecx,1
// 因为类A没有成员变量,所以这个类的大小是1
00007FF694A8299C E8 AA E6 FF FF call operator new (07FF694A8104Bh)
// 这个就是我们调用的operator new方法,里面的内容其实跟上面写的一样
// 后面这个返回值处理,其实跟上面的一样
00007FF694A829A1 48 89 85 28 04 00 00 mov qword ptr [rbp+428h],rax
00007FF694A829A8 48 83 BD 28 04 00 00 00 cmp qword ptr [rbp+428h],0
00007FF694A829B0 74 15 je main+187h (07FF694A829C7h)
00007FF694A829B2 48 8B 8D 28 04 00 00 mov rcx,qword ptr [rbp+428h]
// 不一样的地方,是这样,会主动调用类A的构造函数,并且这个this指针就是上面malloc回来的地址
00007FF694A829B9 E8 61 EA FF FF call A::A (07FF694A8141Fh)
00007FF694A829BE 48 89 85 78 06 00 00 mov qword ptr [rbp+678h],rax
00007FF694A829C5 EB 0B jmp main+192h (07FF694A829D2h)
00007FF694A829C7 48 C7 85 78 06 00 00 00 00 00 00 mov qword ptr [rbp+678h],0
// 后面这个就不重要了,换来换去的
00007FF694A829D2 48 8B 85 78 06 00 00 mov rax,qword ptr [rbp+678h]
00007FF694A829D9 48 89 85 08 04 00 00 mov qword ptr [rbp+408h],rax
00007FF694A829E0 48 8B 85 08 04 00 00 mov rax,qword ptr [rbp+408h]
00007FF694A829E7 48 89 45 48 mov qword ptr [pA],rax
通过我们分析,是不是发现了这个new对象,竟然如此简单,其实就是调用malloc来申请一块内存,然后调用构造函数,完事。
所以不要自己感觉难,就不去学,有些东西学了才发现这么简单。
8.2.4 new一个简单类型数组
前面都是分析new一个变量和对象,这次我们来new一个简单类型的数组
// 8.2.4 new申请数组
// 数组就跟malloc不一样了
int* pNewArrInt = new int[10];
// 这样就申请到了数组,明显这个数组没有初始化
cout << pNewArrInt[0] << endl;
// 初始化0
int* pNewArrInt1 = new int[10]();
cout << pNewArrInt1[0] << endl;
// 初始化0
int* pNewArrInt2 = new int[10] {};
cout << pNewArrInt2[0] << endl;
// 初始化数组
int* pNewArrInt3 = new int[10] {10, 20};
cout << pNewArrInt3[0] << " " << pNewArrInt3[3] << endl;
// 数组释放,这个也需要记得,跟释放一个差别很大
delete[] pNewArrInt; // 需要加一个[]
delete[] pNewArrInt1;
delete[] pNewArrInt2;
delete[] pNewArrInt3;
new数组是这么个写法,把大小写在中括号中,并且不需要自己计算数组大小(一个变量大小*个数)。
那我们就继续好奇,看看new数组和new单独一个变量的源码区别:
175: int* pNewArrInt1 = new int[10]();
00007FF6715B2A91 48 C7 85 88 04 00 00 28 00 00 00 mov qword ptr [rbp+488h],28h
// 还是照样计算数组大小 40=28h
00007FF6715B2A9C 48 8B 8D 88 04 00 00 mov rcx,qword ptr [rbp+488h]
// new的参数,new接受大小的参数
00007FF6715B2AA3 E8 6F E7 FF FF call operator new[] (07FF6715B1217h)
// 这里调用operator new[] 如果是单个变量只是调用operator new
// 后面就是跟之前一样了,只不过这个是数组,需要用到rep stos来初始化
00007FF6715B2AA8 48 89 85 A8 04 00 00 mov qword ptr [rbp+4A8h],rax
00007FF6715B2AAF 48 83 BD A8 04 00 00 00 cmp qword ptr [rbp+4A8h],0
00007FF6715B2AB7 74 22 je main+29Bh (07FF6715B2ADBh)
00007FF6715B2AB9 48 8B BD A8 04 00 00 mov rdi,qword ptr [rbp+4A8h]
00007FF6715B2AC0 33 C0 xor eax,eax
00007FF6715B2AC2 48 8B 8D 88 04 00 00 mov rcx,qword ptr [rbp+488h]
00007FF6715B2AC9 F3 AA rep stos byte ptr [rdi]
00007FF6715B2ACB 48 8B 85 A8 04 00 00 mov rax,qword ptr [rbp+4A8h]
00007FF6715B2AD2 48 89 85 78 06 00 00 mov qword ptr [rbp+678h],rax
00007FF6715B2AD9 EB 0B jmp main+2A6h (07FF6715B2AE6h)
00007FF6715B2ADB 48 C7 85 78 06 00 00 00 00 00 00 mov qword ptr [rbp+678h],0
00007FF6715B2AE6 48 8B 85 78 06 00 00 mov rax,qword ptr [rbp+678h]
00007FF6715B2AED 48 89 85 88 00 00 00 mov qword ptr [pNewArrInt1],rax
观察下来,发现只是调用的operator new[]和operator new的区别。
我们可以看看这个operator new[]源码:
// C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\new_array.cpp
void* __CRTDECL operator new[](size_t const size)
{
return operator new(size);
}
其实就在调用operator new。
8.2.5 new一个类类型数组
上面我们是new了一个简单类型的数组,那我们这次来看看new一个类类型的数组是怎么样的。
// ecx 做为函数第一个参数 但是这个值是0Bh=11个字节,但是我们自己申请的是3个类A的对象,按照一个对象如果没有变量,大小是1个字节,还就是还差8个字节,这8个字节在哪,我们来分析分析
A* pArrA = new A[3](); // 这个大小是0B???
00007FF705F3273B B9 0B 00 00 00 mov ecx,0Bh
00007FF705F32740 E8 F7 ED FF FF call A::operator new[] (07FF705F3153Ch) // operator new返回的是malloc申请11个字节的地址
00007FF705F32745 48 89 85 08 01 00 00 mov qword ptr [rbp+108h],rax // 这个地址通过rax=0x000002968BDF85F0 赋值到 rbp+108h=0x000000FBE24FFAA8的内存地址上,&pArrA就是这个地址,所以我们继续查看这个值:pArrA=0x000002968BDF85F0
00007FF705F3274C 48 83 BD 08 01 00 00 00 cmp qword ptr [rbp+108h],0 // 这个只是一个比较值而已
00007FF705F32754 74 53 je main+89h (07FF705F327A9h) // 判断上面的判断条件,如果等于0就跳转
00007FF705F32756 48 8B 85 08 01 00 00 mov rax,qword ptr [rbp+108h] // 再次把指针的值0000021D31E98DB0存到rax
00007FF705F3275D 48 C7 00 03 00 00 00 mov qword ptr [rax],3 // 把指针指向的位置写成3,这个3其实就是数组的大小,这个个数就直接占了8个字节,牛逼牛逼,所以11个字节是怎么来的
00007FF705F32764 48 8B 85 08 01 00 00 mov rax,qword ptr [rbp+108h] // 这个应该没啥变化
00007FF705F3276B 48 83 C0 08 add rax,8 // 这个偏移8个字节,就刚好就是跳过数组个数的8个字节 RAX = 000002968BDF85F8
00007FF705F3276F 48 8D 0D E1 EB FF FF lea rcx,[A::~A (07FF705F31357h)] // 把析构的地址保存到RCX = 00007FF705F31357
00007FF705F32776 48 89 4C 24 20 mov qword ptr [rsp+20h],rcx // 这是把析构地址放在栈顶0x20字节偏移
00007FF705F3277B 4C 8D 0D 98 EC FF FF lea r9,[A::A (07FF705F3141Ah)] // 把构造函数地址保存到R9 = 00007FF705F3141A
// 后面是函数传参啊
// 函数传参 rcx rdx r8 r9
00007FF705F32782 41 B8 03 00 00 00 mov r8d,3
00007FF705F32788 BA 01 00 00 00 mov edx,1
00007FF705F3278D 48 8B C8 mov rcx,rax
00007FF705F32790 E8 C0 E8 FF FF call `eh vector constructor iterator' (07FF705F31055h) // 这里用了vector来调用三次构造函数,里面的函数怎么实现的就不分析了
00007FF705F32795 48 8B 85 08 01 00 00 mov rax,qword ptr [rbp+108h]
00007FF705F3279C 48 83 C0 08 add rax,8
00007FF705F327A0 48 89 85 18 01 00 00 mov qword ptr [rbp+118h],rax // 把这个地址存到了118h
00007FF705F327A7 EB 0B jmp main+94h (07FF705F327B4h)
00007FF705F327A9 48 C7 85 18 01 00 00 00 00 00 00 mov qword ptr [rbp+118h],0
00007FF705F327B4 48 8B 85 18 01 00 00 mov rax,qword ptr [rbp+118h] // jmp 是跳到这里来的,奇怪,哈哈哈
00007FF705F327BB 48 89 85 E8 00 00 00 mov qword ptr [rbp+0E8h],rax
00007FF705F327C2 48 8B 85 E8 00 00 00 mov rax,qword ptr [rbp+0E8h]
00007FF705F327C9 48 89 45 08 mov qword ptr [pArrA],rax // 最后这个应该是把rax的值赋值到pArrA,偏移了8字节后的地址,就是不知道为啥要存在rbp+0E8h的位置
上图:
经过这个反汇编分析,这次就明白了,operator new函数只调用了一次,但是在malloc的大小的时候,是把整个大小一次malloc出来,包括数组个数占8个字节,还有就是A对象的大小*个数。但是A函数的构造函数,会调用多次,使用了一个vector的方式来调用的。
5.2.6 总结new干了啥
经过我们上面的分析,现在都知道new干了啥了吧,详细的不说了,上图:
8.2.7 new失败处理
我们知道判断malloc失败,直接判断返回值是否为空,那new失败的话,我们怎么判断?
// 8.2.7 new失败处理
#if 0
int* pFile = new int[100000000000];
if (pFile == nullptr) // 这种是按照malloc的方法,通过判断返回值是否为空指针
// 结果成功证明了并没有用
{
// 测试证明没用
cout << "申请失败" << endl;
}
#elif 0 // 第一种,使用异常
try
{
int* pFile = new int[100000000000];
}
catch (bad_alloc& e)
{
cout << e.what() << endl;
}
#elif 1 // 第二种,强制不抛出异常
int* pFile = new(std::nothrow) int[100000000000];
if (pFile == nullptr)
{
cout << "申请失败" << endl;
}
#endif
有两种方法,不过好像我都没看过有使用的,哈哈哈,可能是我看的比较少吧。以后看开源项目的时候,看看有没有。
8.3 delete
上面介绍了几种new的情况了,我们也要跟着来分析一下delete。
8.3.1 delete简单使用
我们来看一下简答释放一个指针
157: // 既然内存已经申请了,最后要释放掉
158: delete pNewInt;
00007FF6043A28EB 48 8B 45 08 mov rax,qword ptr [pNewInt]
00007FF6043A28EF 48 89 85 A8 03 00 00 mov qword ptr [rbp+3A8h],rax
00007FF6043A28F6 BA 04 00 00 00 mov edx,4
00007FF6043A28FB 48 8B 8D A8 03 00 00 mov rcx,qword ptr [rbp+3A8h]
// 也是计算了一个大小,然后调用operator delete,第一参数还是pNewInt
00007FF6043A2902 E8 FF EA FF FF call operator delete (07FF6043A1406h)
00007FF6043A2907 48 83 BD A8 03 00 00 00 cmp qword ptr [rbp+3A8h],0
00007FF6043A290F 75 0D jne main+0FEh (07FF6043A291Eh)
00007FF6043A2911 48 C7 85 38 06 00 00 00 00 00 00 mov qword ptr [rbp+638h],0
00007FF6043A291C EB 13 jmp main+111h (07FF6043A2931h)
// 释放完了会赋一个0x8123的值?
00007FF6043A291E 48 C7 45 08 23 81 00 00 mov qword ptr [pNewInt],8123h
00007FF6043A2926 48 8B 45 08 mov rax,qword ptr [pNewInt]
00007FF6043A292A 48 89 85 38 06 00 00 mov qword ptr [rbp+638h],rax
我们可以追踪operator delete看看:
//C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\delete_scalar_size.cpp/
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block, size_t const) noexcept
{
operator delete(block);
}
又调用了一层
//C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\delete_scalar.cpp
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
#ifdef _DEBUG
_free_dbg(block, _UNKNOWN_BLOCK);
#else
free(block); // 其实就是调用free函数
#endif
}
8.3.2 delete一个类对象
接下来我们看看delete一个类对象
delete pA;
00007FF6043A29CB 48 8B 45 48 mov rax,qword ptr [pA]
00007FF6043A29CF 48 89 85 28 04 00 00 mov qword ptr [rbp+428h],rax
// 原来为啥delete NULL不会保存,是因为编译器这里加了判断
00007FF6043A29D6 48 83 BD 28 04 00 00 00 cmp qword ptr [rbp+428h],0
00007FF6043A29DE 74 1A je main+1DAh (07FF6043A29FAh)
00007FF6043A29E0 BA 01 00 00 00 mov edx,1
00007FF6043A29E5 48 8B 8D 28 04 00 00 mov rcx,qword ptr [rbp+428h]
// 还是还是delete参数
00007FF6043A29EC E8 60 EA FF FF call A::`scalar deleting destructor' (07FF6043A1451h)
00007FF6043A29F1 48 89 85 38 06 00 00 mov qword ptr [rbp+638h],rax
00007FF6043A29F8 EB 0B jmp main+1E5h (07FF6043A2A05h)
// 最后编译器还贴心的,把值赋值成0
00007FF6043A29FA 48 C7 85 38 06 00 00 00 00 00 00 mov qword ptr [rbp+638h],0
A::`scalar deleting destructor’ 这个函数应该是编译器自己生成的函数:
08.1 malloc+new.exe!A::`scalar deleting destructor'(unsigned int):
00007FF6043A2580 89 54 24 10 mov dword ptr [rsp+10h],edx
00007FF6043A2584 48 89 4C 24 08 mov qword ptr [rsp+8],rcx
00007FF6043A2589 55 push rbp
00007FF6043A258A 57 push rdi
00007FF6043A258B 48 81 EC E8 00 00 00 sub rsp,0E8h
00007FF6043A2592 48 8D 6C 24 20 lea rbp,[rsp+20h]
00007FF6043A2597 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
// 析构函数在这里
00007FF6043A259E E8 B9 ED FF FF call A::~A (07FF6043A135Ch)
00007FF6043A25A3 8B 85 E8 00 00 00 mov eax,dword ptr [rbp+0E8h]
00007FF6043A25A9 83 E0 01 and eax,1
00007FF6043A25AC 85 C0 test eax,eax
00007FF6043A25AE 74 11 je A::`scalar deleting destructor'+41h (07FF6043A25C1h)
00007FF6043A25B0 BA 01 00 00 00 mov edx,1
00007FF6043A25B5 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
// operator delete在这里
00007FF6043A25BC E8 45 EE FF FF call operator delete (07FF6043A1406h)
00007FF6043A25C1 48 8B 85 E0 00 00 00 mov rax,qword ptr [this]
00007FF6043A25C8 48 8D A5 C8 00 00 00 lea rsp,[rbp+0C8h]
00007FF6043A25CF 5F pop rdi
00007FF6043A25D0 5D pop rbp
00007FF6043A25D1 C3 ret
这个operator delete调用的跟上面简单使用一样。
8.3.3 delete简单类型数组
我们继续升级,来到了数组。
// 数组释放,这个也需要记得,跟释放一个差别很大
187: delete[] pNewArrInt;
00007FF6043A2C76 48 8B 45 68 mov rax,qword ptr [pNewArrInt]
00007FF6043A2C7A 48 89 85 08 05 00 00 mov qword ptr [rbp+508h],rax
00007FF6043A2C81 48 8B 8D 08 05 00 00 mov rcx,qword ptr [rbp+508h]
// 调用了数组的 operator delete[]
00007FF6043A2C88 E8 CB E5 FF FF call operator delete[] (07FF6043A1258h)
00007FF6043A2C8D 48 83 BD 08 05 00 00 00 cmp qword ptr [rbp+508h],0
00007FF6043A2C95 75 0D jne main+484h (07FF6043A2CA4h)
00007FF6043A2C97 48 C7 85 38 06 00 00 00 00 00 00 mov qword ptr [rbp+638h],0
00007FF6043A2CA2 EB 13 jmp main+497h (07FF6043A2CB7h)
00007FF6043A2CA4 48 C7 45 68 23 81 00 00 mov qword ptr [pNewArrInt],8123h
00007FF6043A2CAC 48 8B 45 68 mov rax,qword ptr [pNewArrInt]
00007FF6043A2CB0 48 89 85 38 06 00 00 mov qword ptr [rbp+638h],rax
我们来看看数组这个:
//C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.34.31933\crt\src\vcruntime\delete_scalar.cpp
_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
#ifdef _DEBUG
_free_dbg(block, _UNKNOWN_BLOCK);
#else
free(block);
#endif
}
数组的直接传了指针,我们前面看到的count是不是没用?
8.3.4 delete类类型数组
我们来到最后一个delete。
delete[] pArrA;
00007FF6043A2E2A 48 8B 85 E8 00 00 00 mov rax,qword ptr [pArrA]
00007FF6043A2E31 48 89 85 C8 05 00 00 mov qword ptr [rbp+5C8h],rax
00007FF6043A2E38 48 83 BD C8 05 00 00 00 cmp qword ptr [rbp+5C8h],0
00007FF6043A2E40 74 1A je main+63Ch (07FF6043A2E5Ch)
00007FF6043A2E42 BA 03 00 00 00 mov edx,3
// rax = pArrA rdx=3
00007FF6043A2E47 48 8B 8D C8 05 00 00 mov rcx,qword ptr [rbp+5C8h]
00007FF6043A2E4E E8 E9 E6 FF FF call A::`vector deleting destructor' (07FF6043A153Ch)
00007FF6043A2E53 48 89 85 38 06 00 00 mov qword ptr [rbp+638h],rax
00007FF6043A2E5A EB 0B jmp main+647h (07FF6043A2E67h)
00007FF6043A2E5C 48 C7 85 38 06 00 00 00 00 00 00 mov qword ptr [rbp+638h],0
A::`vector deleting destructor’这个函数负责释放的了。
08.1 malloc+new.exe!A::`vector deleting destructor'(unsigned int):
00007FF6043A2490 89 54 24 10 mov dword ptr [rsp+10h],edx
00007FF6043A2494 48 89 4C 24 08 mov qword ptr [rsp+8],rcx
00007FF6043A2499 55 push rbp
00007FF6043A249A 57 push rdi
00007FF6043A249B 48 81 EC E8 00 00 00 sub rsp,0E8h
00007FF6043A24A2 48 8D 6C 24 20 lea rbp,[rsp+20h]
00007FF6043A24A7 8B 85 E8 00 00 00 mov eax,dword ptr [rbp+0E8h]
00007FF6043A24AD 83 E0 02 and eax,2
00007FF6043A24B0 85 C0 test eax,eax
00007FF6043A24B2 74 5F je A::`vector deleting destructor'+83h (07FF6043A2513h)
00007FF6043A24B4 4C 8D 0D A1 EE FF FF lea r9,[A::~A (07FF6043A135Ch)]
00007FF6043A24BB 48 8B 85 E0 00 00 00 mov rax,qword ptr [this]
00007FF6043A24C2 4C 8B 40 F8 mov r8,qword ptr [rax-8]
00007FF6043A24C6 BA 01 00 00 00 mov edx,1
00007FF6043A24CB 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
// 前面是在准备参数,
00007FF6043A24D2 E8 78 EC FF FF call `eh vector destructor iterator' (07FF6043A114Fh)
00007FF6043A24D7 8B 85 E8 00 00 00 mov eax,dword ptr [rbp+0E8h]
00007FF6043A24DD 83 E0 01 and eax,1
00007FF6043A24E0 85 C0 test eax,eax
00007FF6043A24E2 74 22 je A::`vector deleting destructor'+76h (07FF6043A2506h)
00007FF6043A24E4 48 8B 85 E0 00 00 00 mov rax,qword ptr [this]
00007FF6043A24EB 48 8B 40 F8 mov rax,qword ptr [rax-8]
00007FF6043A24EF 48 83 C0 08 add rax,8
00007FF6043A24F3 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
00007FF6043A24FA 48 83 E9 08 sub rcx,8
00007FF6043A24FE 48 8B D0 mov rdx,rax
00007FF6043A2501 E8 3F EC FF FF call operator delete[] (07FF6043A1145h)
00007FF6043A2506 48 8B 85 E0 00 00 00 mov rax,qword ptr [this]
00007FF6043A250D 48 83 E8 08 sub rax,8
// 后面好像是跳过去了
00007FF6043A2511 EB 31 jmp A::`vector deleting destructor'+0B4h (07FF6043A2544h)
00007FF6043A2513 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
00007FF6043A251A E8 3D EE FF FF call A::~A (07FF6043A135Ch)
00007FF6043A251F 8B 85 E8 00 00 00 mov eax,dword ptr [rbp+0E8h]
00007FF6043A2525 83 E0 01 and eax,1
00007FF6043A2528 85 C0 test eax,eax
00007FF6043A252A 74 11 je A::`vector deleting destructor'+0ADh (07FF6043A253Dh)
00007FF6043A252C BA 01 00 00 00 mov edx,1
00007FF6043A2531 48 8B 8D E0 00 00 00 mov rcx,qword ptr [this]
00007FF6043A2538 E8 C9 EE FF FF call operator delete (07FF6043A1406h)
00007FF6043A253D 48 8B 85 E0 00 00 00 mov rax,qword ptr [this]
00007FF6043A2544 48 8D A5 C8 00 00 00 lea rsp,[rbp+0C8h]
00007FF6043A254B 5F pop rdi
00007FF6043A254C 5D pop rbp
00007FF6043A254D C3 ret
这个汇编确实复杂。
eh vector destructor iterator’ 这个就不分析了,太复杂了,调用A的析构函数,和operator delete函数。
要想了解的,可以自己分析。
8.3.5 总结delete干了啥
这个就直接上图了。
8.4 new高级玩法
前面的都是一般我们使用的,但是new还有一些比较不常用的,属于高级玩法,我们来了解一下。
8.4.1 重载new
进过上一节的分析,我们看到了new其实是调用了operator new和类的构造函数。
那我们能不能重载new,自己写一个new呢?完全可以的。
还记得我们上一篇写的重载操作符么?operator new也是可以重载的。
8.4.1.1 重载类中的new
class A
{
public:
A()
{
cout << "A构造函数" << endl;
}
// 重载operator new
// 这个size就是mov ecx,11 // 上一节数组
// 这个是申请一个A对象,应是mov ecx,1
void* operator new(size_t size)
{
A* ppoint = (A*)malloc(size);
cout << "operator new: size" << size << endl;
return ppoint;
}
// 这个就是调用的operator[] new,我们下节说
void* operator new[](size_t size)
{
A* ppoint = (A*)malloc(size);
cout << "operator new: size" << size << endl;
return ppoint;
}
~A()
{
cout << "A析构函数" << endl;
}
};
上面代码是两个版本都重载了,一个是new 一个是new数组。
我们写个代码测试一下:
// 8.2.3 new一个对象
A* pA = new A();
delete pA;
// 8.2.5 new一个类类型的数组
A* pArrA = new A[3]();
delete[] pArrA;
就使用上两节课的代码吧,反正都一样
operator new: size1 // 先调用operator new size为1
A构造函数
A析构函数
operator new: size11 // 先调用operator new size为11 就是3+8
A构造函数 // 调用了三次
A构造函数
A构造函数
A析构函数
A析构函数
A析构函数
我突然不想调用我重载的new,想调用全局的new,这也是可以的
// 8.4.1.1 全局new
A* pA2 = ::new A();
::delete pA2;
这种就是调用全局的new和delete
8.4.1.2 重载类中的delete
既然可以重载new,那当然可以重载delete了,我们这波来重载一下。
void operator delete(void* phead)
{
cout << "operator delete: phead:" << phead << endl;
free(phead);
}
void operator delete[](void* phead)
{
cout << "operator delete[]: phead:" << phead << endl;
free(phead);
}
也是类A中的,就不全部拷贝了,代码运行结果:
operator new: size:1
A构造函数
A析构函数
operator delete: phead:000001DCF615CF00
----------------
operator new[]: size:11
A构造函数
A构造函数
A构造函数
A析构函数
A析构函数
A析构函数
operator delete[]: phead:000001DCF615BCF0
----------------
A构造函数
A析构函数
8.4.1.3 重载全局的new
我们上面写过可以使用全局的new,那我们能不能重载全局的new呢?这个也是可以的。(但是这个尽量不要写,知道有这么一回事就行)
// 写在全局里的
void* operator new(size_t size)
{
cout << "全局 operator new: size:" << size << endl;
return malloc(size);
}
void* operator new[](size_t size)
{
cout << "全局 operator new[]: size:" << size << endl;
return malloc(size);
}
还是原来的代码(当然需要把类A重载的屏蔽,因为有类A重载的话,会优先使用类A的),我们来看看运行结果:
全局 operator new: size:1
A构造函数
A析构函数
----------------
全局 operator new[]: size:11
A构造函数
A构造函数
A构造函数
A析构函数
A析构函数
A析构函数
----------------
全局 operator new: size:1
A构造函数
A析构函数
跟我们预期是一样的。
8.4.1.4 重载全局的delete
new都会写了,delete其实也一样的。
void operator delete(void* phead)
{
cout << "全局 operator delete: phead:" << phead << endl;
return free(phead);
}
void operator delete[](void* phead)
{
cout << "全局 operator delete[]: phead:" << phead << endl;
return free(phead);
}
执行看看结果:
全局 operator new: size:1
A构造函数
A析构函数
全局 operator delete: phead:0000016FBBC0F9F0
----------------
全局 operator new[]: size:11
A构造函数
A构造函数
A构造函数
A析构函数
A析构函数
A析构函数
全局 operator delete[]: phead:0000016FBBC0E940
----------------
全局 operator new: size:1
A构造函数
A析构函数
全局 operator delete: phead:0000016FBBC0FAB0
完美,符合预期。
8.4.2 定位new
定位new,比较重要,一般都能看到使用哈哈。
定位new的功能,其实就是在一个块已经分配的内存中初始化一个对象。
需要注意两点:
- 内存已经分配了,定位new不分配内存
- 在已经分配的内存中,初始化一个对象
8.4.2.1 使用
说的太多,都不如直接使用。
// 8.4.2.1 定位new
char pool[100] = { 0 }; // 我申请了一个内存池
// 然后我在pool内存中申请对象A
A* pA3 = new(pool) A();
cout << "pA3: " << pA3 << endl;
new后面需要定位这个内存池在哪,通过gdb看看变量的值。
看到地址都是一模一样的,如果在A中有变量,可以自行尝试,修改了变量的值,这个数组的值是不是也改变了。
8.4.2.2 重载定位new
定位new是不是就介绍完了,其实并不是,在c++中,很多操作符都可以重载,上面的new都可以重载,那我们这个定位new当然也可以重载了。
void* operator new(size_t size, void* phead)
{
cout << "operator new(size_t size, void* phead) size:" << size << "phead:" << phead << endl;
//A* a = (A*)phead;
//return &a[1];
return phead; // 一个简单写法,如果要用这种做内存池,就需要自己维护了
}
这个new就是两个参数了,第一个还是大小,第二个是内存池的指针。
最后注意一点,就是定位new没有delete,因为内存是外部分配的,所以不需要自己分配,有点像内存池的设计。
8.4.3 多种new
其实new还可以随便造的,不过这种写法不推荐大家写,只要自己知道有这么一回事就行。
// main函数中
// 8.4.3 多种new
A* pA5 = new(123, 345) A(); // 莫名其妙的参数,哈哈
delete pA5;
// 类A中
void* operator new(size_t size, int ptv1, int ptv2)
{
cout << "随便造的new size: " << size << " ptv1: " << ptv1 << " ptv2: " << ptv2 << endl;
return malloc(size);
}
// 打印出来
随便造的new size: 1 ptv1: 123 ptv2: 345
A构造函数
A析构函数
全局 operator delete: phead:0000016673EBCCC0
8.4.5 内存池
这个到linux课程,再统一讲。敬请期待。
最后补一个new的链接:https://en.cppreference.com/w/cpp/memory/new/operator_new