new和delete浅析以及与malloc和free区别的简总

new和delete

new/delete是C++动态开辟内存运算符,malloc和free是C库函数实现的动态开辟内存的。

new/delete

new和delete其实也是重载函数,其底层也是调用malloc和free包括new数组和delete数组也是重载new[] 和 delete[] 运算符。也可以自己实现new和delete的重载函数。

那么new单个元素delete单个元素,new数组,delete[]数组到底有什么区别的,混用到底有什么问题,下面我们使用代码来说明问题

// 重载new和new[]
void *operator new(size_t size)
{
	void *p = malloc(size);
	if (p == nullptr) 
		throw bad_alloc();
	else
	{
		cout << "operator new p:" <<  p << endl;
		return p;
	}
}

void *operator new[](size_t size)
{
	void *p = malloc(size);
	if (p == nullptr)
		throw bad_alloc();
	else
	{
		cout << "operator new[] p:" << p << endl;
		return p;
	}
}
// 重载delete和delete[]
void operator delete(void *ptr)
{
	cout << "operator delete ptr:"<<ptr << endl;
	free(ptr);
}

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

对于上面重载的new和delete,现在我们来使用这个说明问题

int main()
{
	int *p = new int(20);
	delete p;

	int *arr = new int[20];
    cout << "arr:" << arr << endl;
	delete []arr;
	return 0;
}

/*
结果:我们正确使用可以发现没有问题,
operator new p:009E5C60
operator delete ptr:009E5C60

operator new[] p:009E5C60
arr:009E5C60
operator delete ptr[]:009E5C60
*/


// 如果我们混用呢?
int main()
{
	int *p = new int(20);
	delete []p;
	

	int *arr = new int[20];
	delete arr;
	return 0;
}
/*结果,我们发现混用也没有问题
operator new p:00E804D0
operator delete ptr[]:00E804D0
operator new[] p:00E85AC8
operator delete ptr:00E85AC8
*/

对于上面的测试代码,我们发现对于int类型来说混用并不影响结果,也就是对于内置类型来说混用是没有问题的,大家可以自行测试上面的代码。

但是我们的C++也是面向对象的语言,那么对于类类型来说,是不是也可以混用呢,我们接着使用代码来测试

// 实现一个简单的类。
class Myclass
{
public:
	Myclass() { cout << "call Myclass()" << endl; }
	~Myclass() { cout << "call ~Myclass()" << endl; }
};


int main()
{
	Myclass *p = new Myclass();
	delete p;

	cout << "======================================" << endl;
	Myclass *arr = new Myclass[5];
	cout << "arr:" << arr << endl;
	delete[]arr;
	return 0;
}

/*
结果
operator new p:00FAE8C8
call Myclass()
p:006AA808
call ~Myclass()
operator delete ptr:00FAE8C8

======================================

operator new[] p:00FAFD60
call Myclass()
call Myclass()
call Myclass()
call Myclass()
call Myclass()
arr:00FAFD64
call ~Myclass()
call ~Myclass()
call ~Myclass()
call ~Myclass()
call ~Myclass()
operator delete ptr[]:00FAFD60
*/
  • 我们使用匹配的new和delete,可以看到对于单个的对象,使用new会开辟内存,然后在这块内存上调用对象的构造函数,在将内存的地址返回。然后在delete的时候,先调用对象的析构函数,然后在释放内存。
  • 对于对象数组而言,我们看到new也是先开辟内存,然后调用对象的构造函数,代码中数组的大小时5,因此调用5次构造函数,然后返回数组的首地址,发现首地址是new中开辟的地址加四字节,然后析构的时候也是调用5次对象的析构,然后在释放内存,而释放的内存却是new中的地址,这是怎么回事呢?
new[] 和 delete []原理

画一个简单的图,来说明这个问题。

在这里插入图片描述

  • new[] 在开辟对象数组时,会多四字节的内存也就是sizeof(int),用来存放数组中对象的个数,然后根据个数调用对象的构造函数,返回数组地址时,是不返回这四个字节的。
  • 在delete[]时,delete会先在从当前的arr的地址减4的位置读取对象的个数,然后根据对象个数调用对象的析构函数,然后在从arr - 4的地址(0x100)的位置free掉内存。
  • 这就是为什么代码中打印的地址时相差4个字节的原因。

对于那个四字节的中的数字到底是不是数组的大小呢?我们用代码测试出来,对于下面的代码大家可以测试一下。

Myclass *arr = new Myclass[5];
cout << "arr:" << arr << endl;  // 将arr强转为int*,然后访问其地址减1的内存的数据,打印出来发现就是5
cout << *((int*)arr - 1) << endl;
delete[]arr;

我们接着测试,是不是上面的代码中因为那个类中都显示的实现了构造函数和析构函数,所以才会出现上面的这种问题呢,那么有朋友可能就会说要不我们实现构造或者析构,或者只实现其中一个呢?看代码

// new 和 delete还是使用上面我们自己重载的
// 我们先测试类中没有显示的构造和析构函数,为了让我们的类看起来像个类,我们加一个show方法。
class Myclass
{
public:
	void show() { cout << "call Myclass::show" << endl; };
};

int main()
{


	Myclass *p = new Myclass();
	cout << "p:" << p << endl;
	p->show();
	delete p;

	cout << "======================================" << endl;
	Myclass *arr = new Myclass[5];
	cout << "arr:" << arr << endl;
	for (int i = 0; i < 5; ++i)
		arr[i].show();


	delete[]arr;
	return 0;
}

/*
operator new p:00C4A978
p:00C4A978
call Myclass::show
operator delete ptr:00C4A978
======================================
operator new[] p:00C4F650
arr:00C4F650
call Myclass::show
call Myclass::show
call Myclass::show
call Myclass::show
call Myclass::show
operator delete ptr[]:00C4F650
*/

我们看到对于没有实现显示构造和析构的类,在开辟对象数组式,new开辟的内存和返回的内存是相同的,并没有多开辟四字节去存放对象的个数。那如果实现一个呢?看代码

// 我们只显示实现构造函数,不实现析构函数
class Myclass
{
public:
	Myclass() {
		cout << "call Myclass()" << endl;
	}
};

int main()
{


	Myclass *p = new Myclass();
	cout << "p:" << p << endl;
	delete p;

	cout << "======================================" << endl;
	Myclass *arr = new Myclass[5];
	cout << "arr:" << arr << endl;


	delete[]arr;
	return 0;
}

/*
operator new p:011804D0
call Myclass()
p:011804D0
operator delete ptr:011804D0
======================================
operator new[] p:0118F618
call Myclass()
call Myclass()
call Myclass()
call Myclass()
call Myclass()
arr:0118F618
operator delete ptr[]:0118F618
*/
// 我们发现还是没有开辟多余的四个字节内存存放对象的个数。

//================================================================================


// 如果我们只实现析构呢,
class Myclass
{
public:
	~Myclass() {
		cout << "call ~Myclass()" << endl;
	}
};

int main()
{


	Myclass *p = new Myclass();
	cout << "p:" << p << endl;
	delete p;

	cout << "======================================" << endl;
	Myclass *arr = new Myclass[5];
	cout << "arr:" << arr << endl;


	delete[]arr;
	return 0;
}

/*
operator new p:00E3E590
p:00E3E590
call ~Myclass()
operator delete ptr:00E3E590
======================================
operator new[] p:00E3F050
arr:00E3F054
call ~Myclass()
call ~Myclass()
call ~Myclass()
call ~Myclass()
call ~Myclass()
operator delete ptr[]:00E3F050
*/
// 我们惊讶的发现那个四字节的内存配开辟了。

我们发现只要类显示的实现了析构函数,在开辟对象数组时,new就会多开辟四个字节的内存,用于存放数组中对象的个数。

总结

  • 对用内置类型来说,使用new和delete之间的混用,对用结果是没有影响的。
  • 对于类类型来说,只要类中显示的实现了析构函数,delete 和 delete[] 就不能混用。大家可以根据上面那个图想想,如果是new一个单个对象,然后使用delete[]去释放,那个delete会先去当前地址减4的位置去获取大小,而这个内存的的值是个未知数啊,因此肯定会出现问题,同样如果new一个对象数组,然后使用delete去释放,delete直接从当前位置调用析构,然后释放,这就导致只会有一个对象调用析构,而内存的释放肯定是要出现问题的。
  • 所以对于内存中的前四字节中,我们可以认为他是为delete[]准备的,这样就可以知道需要调用几次析构了。

另外附上new和malloc,delete和free的区别的总结

new和malloc的区别

  • malloc开辟内存是按照字节开辟的,对于返回的内存是需要进行类型强转的,因为返回时void*。
  • new内存开辟直接就是根据类型去开辟的,并且new开辟内存可以初始化,对于new一个对象的时候,不仅开辟内存而且调用构造函数。
  • malloc开辟内存失败时返回一个空指针,需要程序自己去判断。
  • new开辟内存失败后,会抛出bad_alloc异常。
  • malloc开辟数组时也是按照字节开辟的。
  • new开辟数组时,也是需要指定类型,然后根据类型开辟数组。并且可以初始化为0.
int *p = (int*)malloc(sizeof(int));
int *p1 = new int;
int *p2 = new int(20); // 初始化

int *arr1 = (int*)malloc(sizeof(int) * 20);
int *arr2 = new int[20];
int *arr2 = new int[20](); // 初始化为0

free和delete的区别

  • free的形参是一个void*类型,因此无论是指向变量的指针还是一个指向数组的指针释放都是一样的。
  • delete对于释放指向变量的指针和指向数组的指针的释放时不一样的,对于指向数组的指针的释放需要指定delete[] p。
  • 对于指针指向是对象是,delete先会调用对象的析构函数,然后在释放内存,而free只释放内存不会调用析构函数。

做个简单的总结,希望对您有用,如果有错误,请您留言讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值