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

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

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


文章目录


前言

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


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

c 复制代码
 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全局函数来释放空间。下面我们看一些底层代码的实现。

c 复制代码
/*
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()最大的区别就是对自定义类型进行操作(接下来我会围绕这个简单栈进行讲解)

c 复制代码
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便认定指针指向一个数组,否则它便认定指针指向单一对象:

c 复制代码
   Stack* st1 = new Stack[10];
   Stack* st2= new Stack;
   delete[] st1;
   delete st2;

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

如果你没有对st1使用"delete []"形式,又会发生什么事呢?唔,其结果亦未有定义,但你可以猜想可能导致太少的析构函数被调用。

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

总结

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

相关推荐
biomooc15 分钟前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人17 分钟前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
black^sugar19 分钟前
纯前端实现更新检测
开发语言·前端·javascript
404NooFound24 分钟前
Python轻量级NoSQL数据库TinyDB
开发语言·python·nosql
用余生去守护1 小时前
python报错系列(16)--pyinstaller ????????
开发语言·python
yuanbenshidiaos1 小时前
c++---------数据类型
java·jvm·c++
数据小爬虫@1 小时前
利用Python爬虫快速获取商品历史价格信息
开发语言·爬虫·python
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
莫名其妙小饼干2 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
十年一梦实验室2 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵