当前位置: 首页 > article >正文

new/delete和malloc()/free()的区别及其使用

C++系列----new/delete和malloc()/free()的区别

这篇文章我将深读刨析一下这二者的区别及其在使用过程中应该注意的事项


文章目录

  • C++系列----new/delete和malloc()/free()的区别
  • 前言
  • 一、new/delete和malloc/free在操作自定义类型时的区别
    • 1.1、在属性和使用上的区别
    • 1.2、返回类型的区别
    • 1.3内存申请失败时的返回值
    • 1.4、定义对象过程
  • 二、operator new和operator delete
  • 三、new和delete实现原理
    • 拓展
  • 四、在使用new/delete、new[]/delete[]时要匹配使用
  • 总结


前言

在c\c++程序的开发过程中,动态开辟和管理内存是我们无法避免 的操作,而在C语言和C++中都提供了,相应的操作方法,那么我们该如何选择呢?要如何选择我们首先要知道他们各自的”能力“大小


一、new/delete和malloc/free在操作自定义类型时的区别

 void Test()
 {
     // 动态申请一个int类型的空间
     int* ptr4 = new int;
     // 动态申请一个int类型的空间并初始化为10
     int* ptr5 = new int(10);
     // 动态申请10个int类型的空间
     int* ptr6 = new int[10];
     delete ptr4;
     delete ptr5;
     delete[] ptr6
     }

在这里插入图片描述

如果你不了解或忘记new的基本操作,请用上述代码回顾一下

1.1、在属性和使用上的区别

  • new/delete是关键字需要编译器支持,malloc/free是库函数,需要头文件支持
  • 在使用malloc()申请空间是,我们要明确指出申请空间大小,使new则不需要,编译器会自动推演其大小

1.2、返回类型的区别

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void* 需要通过强制类型转换将void*指针转换成我们需要的类型。所以在C++程序中使用new会比malloc安全可靠。

1.3内存申请失败时的返回值

malloc分配内存失败时返回NULL,我们可以通过判断返回值可以得知是否分配成功;new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL,分配失败时如果不捕捉异常,那么程序就会异常退出,我们可以通过异常捕捉的方式获取该异常。(异常这块知识,在后面我会介绍)

1.4、定义对象过程

使用new来操作自定义类型时会有三步:

  1. 调用operator new 函数(对于数组是operator
    new[])分配一块内存空间(底层默认使用malloc实现)以便存储特定类型的对象;
  2. 编译器运行相应的构造函数以构造对象(对对象进行初始化)。
  3. 对象构造完成后,返回一个指向该对象的指针。

使用delete操作符来释放对象内存时会经历两个步骤:

  1. 调用对象的析构函数。
  2. 编译器调用operator delete(或operator delete[])函数释放内存空间

二、operator new和operator delete

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。下面我们看一些底层代码的实现。

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
			static const std::bad_alloc nomem;
			_RAISE(nomem);
		}
	return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
	_CrtMemBlockHeader* pHead;
	RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
	if (pUserData == NULL)
		return;
	_mlock(_HEAP_LOCK); /* block other threads */
	__TRY
		/* get a pointer to memory block header */
		pHead = pHdr(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_FINALLY
		return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)//free函数定义的宏

可以看到operator new的底层也是使用malloc()申请空间的,operator delete的底层是使用free()来实现的。

三、new和delete实现原理

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

new/delete与malloc()/free()最大的区别就是对自定义类型进行操作(接下来我会围绕这个简单栈进行讲解)

class Stack {
    Stack(int n=4)
    {
        _arr = new int[4];
        cout << "Stack" << endl;
    }
    ~Stack()
    {
        delete[] _arr;
        cout << "~Stack" << endl;
    }
private:
    int *_arr;
    int _capacity;
    int _size;
};

在这里插入图片描述
结合上面(new)的三个步骤,我们可以看到使用malloc()申请,得到的st1指向的那块空间并没有被初始化,只是开辟了同Stack类型等大的空间(这里可以看到成员变量是编译器优化的结果,可以使用显式调用析构函数来验证,程序会报错的),由new得到的st2值向的空间,完成了初始化。

在这里插入图片描述
从上面打印结果我们可以看到,在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会,这就导致在使用free()对自定义类型对象进行销毁时,其成员所申请的空间(这里指_arr指针指向的空间)并不会被释放。

拓展

在这里插入图片描述

让我们从汇编语言的角度看一下程序的执行,上面我们说过operator new其实就是将malloc()进行了封装,而new其实就可以理解是将operator new与析构函数封装到一起。

四、在使用new/delete、new[]/delete[]时要匹配使用

注:看之前需要了解new可以一次开辟多个对象

当我们使用delete对申请对象进行清理时
delete的最大问题在于:即将被删除的内存之内究竟存有多少对象?这个问题的答案决定了有多少个析构函数必须被调用起来。
实际上这个问题可以更简单些:即将被删除的那个指针,所指的是单一对象或
对象数组?这是个必不可缺的问题,因为单一对象的内存布局一般而言不同于数组
的内存布局。更明确地说,数组所用的内存通常还包括“数组大小”的记录,以便
delete知道需要调用多少次析构函数。单一对象的内存则没有这笔记录。你可以把
两种不同的内存布局想象如下图:
在这里插入图片描述

其中n用于记录,申请对象的个数,也是用n来得知需要析构几次(怎么得知n呢,这是编译器底层使用偏移量来完成的)如果你使用delete[]来清理单一对象,那么编译器不会知道需要调用几次析构函数,随之就会造成各种问题。

当你对着一个指针使用 delete,唯一能够让 delete知道内存中是否存在一个“数组大小记录”的办法就是:由你来告诉它。如果你使用delete时加上中括号delete便认定指针指向一个数组,否则它便认定指针指向单一对象:

   Stack* st1 = new Stack[10];
   Stack* st2= new Stack;
   delete[] st1;
   delete st2;

如果你对st2使用"delete []"形式,会发生什么事?结果未有定义,但不太可能让人愉快。假设内存布局如上,delete会读取若干内存并将它解释为“数组大小”然后开始多次调用析构函数,浑然不知它所处理的那块内存不但不是个数组,也或许并未持有它正忙着销毁的那种类型的对象
如果你没有对st1使用"delete []"形式,又会发生什么事呢?唔,其结果亦未有定义,但你可以猜想可能导致太少的析构函数被调用。

所以我们在使用它们时一定要严格匹配

总结

本次博客的分享就到这里了,博主自身能力还有很多不足有很多知识,并不能很自然、流畅的给大家分享出来。


http://www.kler.cn/a/378149.html

相关文章:

  • Swift 开发教程系列 - 第5章:集合类型
  • C# 独立线程
  • mysql之命令行基础指令
  • 十六届蓝桥杯嵌入式资料 看这个就够了(附CSDN开源程序)
  • 自研小程序-心情追忆
  • 基于SpringBoot+Gpt个人健康管家管理系统【提供源码+答辩PPT+参考文档+项目部署】
  • 无人机航拍铁路障碍物识别图像分割系统:创新焦点发布
  • 将分类标签转换为模型可以处理的数值格式
  • Android 蓝牙连接 HID 设备
  • 【RAG】自动化RAG框架-“AutoML风”卷到了RAG?
  • 基于Android13源码分析Launcher启动
  • java多线程编程(二)一一>线程安全问题, 单例模式, 解决程线程安全问题的措施
  • FRAMES数据集:由谷歌和哈佛大学 联合创建一个综合评估数据集,目的测试检索增强生成系统在事实性、检索准确性和推理方面的能力
  • .card ~ img { width: 100%; height: 100%; object-fit: cover; }
  • git入门教程12:git命令与技巧
  • 论 ONLYOFFICE:开源办公套件的深度探索
  • PyTorch实战-手写数字识别-CNN模型
  • 【已解决,含泪总结】Ubuntu18.04下非root用户Anaconda3卸载重装,conda install终于不再报错
  • 可编辑31页PPT | 智慧业务中台规划建设与应用总体方案
  • 大厂面试真题-MVCC有哪些不好
  • 小白从零开始配置pytorch环境
  • Apache 负载均衡详细配置步骤
  • StringTable
  • 利用ExcelJS封装一个excel表格的导出
  • git 入门作业
  • 学习记录:基于Z-Stack 3.0.1的Zigbee智能插座实现