八、c++学习(加餐4:深入分析new和delete)

news2024/9/23 13:22:11

经过了两篇的类和对象分析,我们这一篇再次加餐,对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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710509.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

技术干货——Selenium Python使用技巧(三)

目录 处理不同情况的等待 网页中的滚动操作 使用Selenium放大和缩小 查找元素的大小 获取元素的X和Y坐标 使用自定义配置文件禁用JavaScript 设置手动代理设置 总结&#xff1a; 处理不同情况的等待 在Selenium自动化测试中网页可能需要花费一些时间来加载&#xff0c;…

MybatisPlus从入门到精通-基础篇

文章目录 一、概述二、快速入门2.1 数据库准备2.2 创建springboot工程2.3 实体类准备2.4 测试MybatisPlus 三、MP常用配置3.1 设置表映射规则3.2 设置主键生成策略3.3 设置字段映射关系3.4 设置字段和列名的驼峰映射3.5 日志 四、基本使用4.1 增加&#xff08;插入&#xff09;…

TF卡/U盘扩容

1. 问题 在使用大于镜像、对TF卡/U盘烧录镜像以后&#xff0c;TF卡/U盘会出现一部分的空闲内存无法被使用&#xff0c;导致出现使用空间不足的报错&#xff0c;或运行大型项目不成功。 注意&#xff1a;本教程仅针对自行烧录镜像的用户&#xff0c;TF卡/U盘内如有出厂镜像则可…

【数据挖掘】时间序列教程【四】

3.3 划分变体 我们可以对上述 的主模型采用方差分析方法,并将中的总变异分解为 为残差平方和和可归因于各种频率的变化。 第二行是可能的,因为平方的所有交叉项都等于零,即对于所有 ,

基于JavaSwing的五子棋游戏设计

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/87987074?spm1001.2014.3001.5503 运行截图&#xff1a;

4.22. 卷积定理

1. 时域&#xff1a; 我们知道卷积运算是为了求系统的零状态响应的&#xff0c;即&#xff0c;如果输入给系统的信号是f(t)&#xff0c;系统函数是h(t)&#xff0c;那系统的输出是什么&#xff1f; 就是按照上述方式卷积得到 那上述的卷积在频率域是什么呢&#xff1f; 2. 卷积…

windows系统下的nvm环境安装

1、下载 https://github.com/coreybutler/nvm-windows/releases 直接下载zip包 并安装 2、安装 注意&#xff1a;尽量按照默认路径安装 否则可能出现 nvm 安装完成 后面下载使用node的时候有问题 3、安装完成检测 打开cmd命令 输入 nvm -v出现版本号 则安装成功 4、se…

FullGC调优100倍,掌握这3招,吊打JVM调优

前言&#xff1a; 在40岁老架构师尼恩的读者社区&#xff08;50&#xff09;中&#xff0c;很多小伙伴拿不到offer&#xff0c;或者拿不到好的offer。 尼恩经常给大家 优化项目&#xff0c;优化简历&#xff0c;挖掘技术亮点。 在指导简历的过程中&#xff0c; 线上问题排查…

【实用教程】教你一招 IDE 中比较骚的操作技巧!

我靠&#xff0c;这是个高手&#xff01;这真是个高手&#xff01; IDEA 有个很牛逼的功能&#xff0c;那就是后缀补全&#xff08;不是自动补全&#xff09;&#xff0c;很多人竟然不知道这个操作&#xff0c;还在手动敲代码。 这个功能可以使用代码补全来模板式地补全语句&…

02_04_02实时调度类_线程优先级代码实战

知识回忆 基础知识 Linux内核当中有3种调度策略: . SCHED_ OTHER分时调度策略;(普通进程) SCHED_ FIFO 实时调度策略,先到先服务; SCHED RR实时调度策略&#xff0c;时间片轮转。 备注:如果有相同优先级的实时进程(根据优先级计算的调度权值是一样的)已经准备好&#xff0c;FI…

vim和vimplus安装详细步骤

1、vim安装 sudo apt update sudo apt install vim依次执行以上命令&#xff0c;安装好vim编辑器&#xff0c;安装好之后&#xff0c;直接使用vim hello.c进行测试&#xff0c;如果可以进入就没有问题。 2、vimplus安装 2.1 检查vim版本 vim --version vim --version | gre…

微信小程序入门教程

微信小程序入门教程 1、前言1. 相关介绍2. 开发工具 2、微信小程序注册3、 构建第一个微信小程序3.1 微信开发者工具3.1.1 小程序创建3.1.2 小程序项目结构目录介绍 3.2 Hbuilder 4、小程序的发布 1、前言 1. 相关介绍 要学习制作微信小程序&#xff0c;首先要先了解微信公众…

【数据库工具】 图文版介绍Xampp工具的使用实战

前言 有时候懒得安装数据库&#xff0c;就可以使用一些集成工具&#xff0c;比如XAMPP就是一个流行的软件包&#xff0c;便于搭建本地web环境&#xff0c;使用里面的mysql也是相当方便&#xff0c;今天我们就一起来看一下。 &#x1f3e0;个人主页&#xff1a;我是沐风晓月 &…

使用 Keil 环境来写 EK TM4C123G 代码

EK TM4C123G 处理器介绍 就这么一个红板子&#xff0c;上边有两个处理器芯片&#xff1a; 靠上边的芯片&#xff0c;用作仿真/调试器&#xff0c;可以先忽略&#xff1b; 我们重点关注的芯片&#xff0c;位于板子下侧中间。 从丝印上&#xff0c;可以看出芯片的型号为&#…

软件UI工程师的职责模板

软件UI工程师的职责模板1 职责&#xff1a; 1.负责产品的UI视觉设计(手机软件界面 网站界面 图标设计产品广告及 企业文化的创意设计等); 2.负责公司各种客户端软件客户端的UI界面及相关图标制作; 3.设定产品界面的整体视觉风格; 4.为开发工程师创建详细的界面说明文档&…

网联V2X跟踪式微波雷达使用说明书

1 设备简介 网联 V2X跟踪式微波雷达跟踪式微波雷达传感器&#xff0c; 主要应用于高速公路、城市道普通公路等场景&#xff0c; 通过发射 FMCW调频连续波信号调频连续波信号 &#xff0c;接收路面目标物的回波信号获取目标物的距离、速度和角信息。 传感器通过 以太网或者光纤 …

Linux MQTT环境搭建详细步骤

关于MQTT的安装之前写过一次&#xff0c;但是不够详细&#xff0c;这里重新补充一下&#xff0c;以后用到的时候更方便。 1. 安装MQTT服务器 上网搜索apache activemq&#xff0c;找到它的官网https://activemq.apache.org/。 下载Linux版本。写文档时版本为ActiveMQ 5.18.1 …

改进的白鲸优化算法

改进的白鲸优化算法 一、算法灵感二、算法介绍2.1 初始化2.2 探索阶段2.3 开发阶段2.4 鲸落阶段 三、改进的白鲸优化算法3.1 集体行动策略3.2 小孔成像策略3.3 二次插值策略3.4 IBWO伪代码 一、算法灵感 白鲸优化算法(Beluga whale optimization, BWO)是2022年提出的一种元启发…

面试必备之安卓APP测试知识大全(值得收藏)

目录 一、安卓系统知识概述 1.1 安卓系统架构 1.2 安卓权限系统 1.3 认识adb与安卓间的通信 二、安卓APP测试流程图&#xff08;转&#xff09; 三、安卓App测试点 3.1 UI测试 3.2 兼容性测试 3.3 安装卸载/本地升级测试OTA 3.4 版本在线升级测试FOTA 3.5 交互测试 …

记录--让整个网站界面无滚动条

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 界面无滚动条 滚动条的优化也有很多种&#xff0c;比如随便再网上搜索美化浏览器滚动条样式&#xff0c;就会出现些用css去美化滚动条的方案。 那种更好呢&#xff1f; 没有更好只有更合适 像默认的滚…