C++:深入理解operator new/operator delete

1.语法层面

1.基本语法

class Node
{
public:
	Node(int val)
		:_val(val),_next(nullptr)
	{
	}
private:
	int _val;
	Node* _next;
};

void test()
{
	int* p1 = new int;                 //申请空间
	int* p2 = new int(1);              //申请空间 + 初始化
	int* p3 = new int[4];              //申请4个int整型空间
	int* p4 = new int[4] {1, 2, 3, 4}; //申请4个整型空间 + 初始化
	Node* n1 = new Node(2);            //申请空间 + 调用构造函数
	delete p1;
	delete p2;
	delete[] p3;
	delete[] p4;
	delete[] n1;
}

在这里插入图片描述

注意点

1.申请单个空间时使用new/delete,申请一段连续空间时使用new[ ]/delete[ ],且一定要匹配使用
2.对于自定义类型,new会调用构造函数,delete会调用其的析构函数

2.new/delete和malloc/free的区别

用法上:

(1)new/delete可以自定义初始化,而malloc/free只负责开空间,不会初始化,且用法上比malloc/free方便

(2)new/delete不需要手动计算申请空间的大小,直接在后面加上类型即可

(3)new/delete不需要强转,malloc和free由于返回值类型为void*,因此需要强转使用

(4)new/delete在实现时使用了抛异常的机制,可以更好的处理问题,而malloc/free则需要手动实现判断

(5)new/delete可以更好的处理自定义类型,对于自定义类型,new的时候调用它的构造函数,delete的时候调用它的析构函数,同时new的时候还可以通过隐式类型转换调用它的有参构造,这对于链表的构造非常方便

(6)new/delete是操作符,malloc和free是函数

3.operator new和operator delete函数(底层重点)

1.operator new/delete原理

在这里插入图片描述

2.图解

1.new/new[]

new/new[]的调用规则如下图。

在这里插入图片描述

2.delete/delete[]

delete/delete[]的调用规则如下图。

在这里插入图片描述

new/delete操作符在二进制指令上本质是调用operator new和operator delete函数,而operator new在底层是对malloc的封装(operator delete)类似。

注意:delete和delete[]必须先调用析构函数,否则对于栈这样的类,它们申请的空间无法释放

在这里插入图片描述

3.new[n]和delete[]

在使用new[n]时,编译器可以通过n确定调用多少次构造函数,但是delete[]时我们并不会给值,那么调用delete[]时编译器如何知道调用多少次析构函数呢。

先看如下代码。

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

int main()
{
	A* a = new A[10];
	delete[] a;
	return 0;
}

按照计算,每一个A中有一个_a,为4个字节,那么申请10个A应该为40字节,可是我们观察汇编,却发现申请了48字节(30为16进制表示),那么多余的8个字节是干什么的呢?

在这里插入图片描述

以下为a的地址,通过内存窗口我们可以看到确实开出了10个整型的空间。

在这里插入图片描述

但是如果再向上看一点,就会发现我们多开出的8个字节。其中存储了十六进制的a(即十进制的10),即为我们需要调用构造函数和析构函数的次数。
在这里插入图片描述

总结:

在我们调用new[]时,会有一个接收的地址,这个地址是我们开辟好的空间的第一个位置的地址,但是编译器其实在这个位置之前多开了字节来存储对象的个数,这样在调用delete[]时就可以通过这个值知道调用多少次析构函数了。如果使用delete[] 来释放空间,那么其在内部会从a指向的空间再向前移动的位置开始释放空间,而不是从a处直接释放空间,如果从a处直接释放空间则会报错。

那么如果不匹配使用,比如:

在这里插入图片描述

此时,由于调用的delete,因此它不会进行指针向前移动字节,而是直接从a的位置开始释放,而申请的空间不能分割释放(也就是释放位置错了),因此会报错。

在这里插入图片描述

上述情况是在我们显式写了析构函数的情况下,==如果我们没有显式写析构函数,那么由编译器默认生成的析构函数没有作用,因此编译器会优化掉,不会调用析构函数,也不会去存储调用次数,不会多申请那一部分空间,==因此不会报错。

从下图可以看到没有多申请空间来存储个数。

在这里插入图片描述

注意:

​ 新版编译器会给出警告如图:

在这里插入图片描述

4.定位new

1.定义

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

可以理解为这块空间我先拿到它的使用权,但是我先不初始化使用,等到需要使用的时候再调用定位new。

2.使用格式

new (place_address) type或者new (place_address) type(initializer-list)

注:place_address必须是一个指针,initializer-list是类型的初始化列表

class A
{
public:
	A(int a = 0)
 		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
 		cout << "~A():" << this << endl;
	}
private:
	int _a;
};

nitializer-list是类型的初始化列表

class A
{
public:
	A(int a = 0)
 		: _a(a)
	{
		cout << "A():" << this << endl;
	}
	~A()
	{
 		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
  • 32
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值