理解malloc、free、new、delete

mallocnew的区别

  1. malloc是按字节开辟空间的,new开辟内存时需要指定类型(new int()),malloc开辟内存返回的都是void*,而new返回的是对应类型的指针

  2. malloc负责开辟空间,new不仅有malloc的功能,还可以进行数据初始化和构造对象,比如:new int(10)new有开辟空间和构造的功能。

  3. malloc开辟内存失败返回nullptr,而new则会抛出bad_alloc异常

  4. 我们调用new实际上是调用的operator new,可以重载operator new

freedelete的区别

  1. delete先调用析构函数,再释放空间(即free

  2. 我们调用delete实际上是调用的operator delete,可以重载operator delete

我们先重载一下new、delete,添加测试类

// new实际上先调用operator new开辟内存空间,然后调用对象的构造函数
void* operator new(size_t size) {
	void* p = malloc(size);
	if (p == nullptr) {
		throw bad_alloc();
	}
	cout << "operator new addr:"<< p << endl;
	return p;
}

// delete实际上先调用对象的析构函数,然后调用operator delete回收内存空间,
void operator delete(void* ptr) {
	cout << "operator free addr:" << ptr << endl;
	free(ptr);
}

// 用于数组
void* operator new[](size_t size) {
	void* p = malloc(size);
	if (p == nullptr) {
		throw bad_alloc();
	}
	cout << "operator new[] addr:" << p << endl;
	return p;
}


void operator delete[](void* ptr) {
	cout << "operator free[] addr:" << ptr << endl;
	free(ptr);
}

class Test {
public:
	Test(int data = 10) 
		:ma(data)
	{
		cout << "Test()" << endl;
	}
	~Test() {
		cout << "~Test()" << endl;
	}

private:
	int ma;
};

存在自定义构造析构函数,new[]

int main() {
	try {
		Test* p = new Test[5];
		cout << "开辟空间后返回的地址:" << p << endl;
		delete[] p; // 调用析构,不会查看前4个字节确定数组元素的个数
		// delete[]p;   //会查看前4个字节确定数组元素的个数
	}
	catch (const bad_alloc& err) {
		cerr << err.what() << endl;
	}
	return 0;
}

在这里插入图片描述
可以看到,我们要分配5个Test对象的空间,这5个对象的空间为20字节,而size却是24,还多出了4字节空间。是的,malloc就是需要多分配4字节空间用于存储对象的个数,以便delete[]执行指定次数析构函数

delete正确执行析构函数了,还要释放空间,即free,那free怎么知道要释放多少空间?

malloc会多分配4字节空间用于存放5个Test对象的个数,然后返回开辟空间的地址,在这个地址之前还有8字节,叫做块头,存放了真正给用户动态开辟的空间大小,也就是24

delete[]在释放空间的时候,会在用户传入地址往前查看4字节,执行指定次数析构函数后再执行free,free会在这24字节空间再往前偏移8字节,查看需要释放的内存大小(24),连同块头的8字节一起释放
在这里插入图片描述

当前场景下是内存这样,而为了防止病毒任意修改内存信息,某些平台可能会做一定的优化,比如在块头和分配的内存之间存在的填充

无自定义析构函数,new[]

测试类如下:

class Test {
public:
	Test(int data = 10) 
		:ma(data)
	{
		cout << "Test()" << endl;
	}
	
private:
	int ma;
};

测试代码如下:

int main() {
	try {
		Test* p = new Test[5];
		cout << "开辟空间后返回的地址:" << p << endl;
		delete[] p; // 调用析构,不会查看前4个字节确定数组元素的个数
		// delete[]p;   //会查看前4个字节确定数组元素的个数
	}
	catch (const bad_alloc& err) {
		cerr << err.what() << endl;
	}
	return 0;
}

在这里插入图片描述
此时传入new[]的size变成了20,少了我们之前用于存储对象个数的4字节空间
在这里插入图片描述
对于内置的数据类型和没有自定义析构函数的类类型,new和new[]分配空间的时候,不会额外分配4字节存储对象的个数,依然会存在8字节的块头。只要是不额外分配4字节存储对象的个数,new和new[]、delete和delete[]混用不会出错

开辟方式释放方式结果
newdelete成功
new[]delete内嵌类型和不提供自定义析构函数成功(new[]不会产生4字节记录元素数量,delete不往前偏移4B查看元素数量);
提供自定义析构函数失败(new[]会产生4字节记录元素数量,delete不往前偏移4B查看元素数量)
newdelete[]内嵌类型和不提供自定义析构函数成功(new不会产生4字节记录元素数量,delete[]不往前偏移4B查看元素数量);
提供自定义析构函数失败(new不会产生4字节记录元素数量,delete[]往前偏移4B查看元素数量)
new[]delete[]成功

对于提供自定义析构函数的类类型,用new[]开辟空间,用delete释放空间出错。new[]会开辟4字节存储对象的个数,执行delete时,直接往前偏移8字节获取开辟的内存大小,由于存在4字节内存存储对象的个数,实际需要偏移12字节才能获取块头信息进行空间正确释放。此外,只执行可一个对象的析构函数,而new[]构造了一个对象数组,这也不正确。

同理,对于提供自定义析构函数的类类型,用new开辟空间,用delete[]释放空间也出错。因为new没有开辟4字节存储对象个数,而delete以为存在这4字节,执行析构函数的次数就从块头获取了,这不正确。想要访问块头,执行delete[]时会偏移12字节,越过了块头,这也会出问题。

参考:为什么new/delete和new[]/delete[]必须配对使用?

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值