C++ new和delete原理及应用!

C语言中我们在堆上开辟内存时,用到的是malloc和free,可是在C++中却引入了新的开辟内存和释放内存的方法,即new和delete。那么它们之间有什么不一样,原理又是怎样的呢?

一、malloc/free和new/delete的区别?

1、malloc和free是函数,new和delete却是C++的运算符

2、malloc只能开辟空间,new既能开辟空间还可以初始化。new一次会调用一次构造函数,delete一次也会调用一次析构函数。

3、malloc内存开辟失败时会返回NULL指针,但new开辟内存失败时不返回NULL指针,而是抛出bad_alloc类型的异常

4、malloc在堆上申请的内存是void*类型,需要强转成自己需要的类型。new则在申请内存的时候就已经明确要申请的内存类型。

二、C语言中已经有了malloc/free,C++中为什么还要有new/delete?

1、首先malloc/free,new/delete都可以用于申请动态内存和释放内存。对于非内置数据类型的对象而言,malloc/free无法满足此种动态对象的要求。因为对象在创建的同时会自动调用构造函数,消亡之前会自动调用析构函数。因为malloc/free是库函数而不是运算符,不在编译器控制权限内,不能把构造函数和析构函数的任务强加于malloc/free。但是C++中new可以完成动态内存分配和初始化工作,delete能够完成清理与释放内存的工作。

2、此时又有了一个新的问题,既然new/delete的功能可以覆盖掉malloc/free,为什么C++中不把malloc/free淘汰掉呢?

因为C++程序经常要调用C函数,而C程序只能用malloc/free来管理动态内存。而且malloc/free,new/delete一定要配对起来使用。如果用free来释放new出来的内存,那么很有可能因为无法析构new创建的对象来导致程序出错。如果用delete来释放malloc申请的动态内存,虽然程序不会出错,但是会导致程序可读性变差。

三、C++中 new/delete运算符重载

#include<iostream>
#include<list>
#include<map>
using namespace std;

//只负责内存开辟
void *operator new(size_t size)
{
	cout << "operator new" << endl;
	void *p = nullptr;
	p = malloc(size);
	if(p == nullptr)
		throw bad_alloc();
	return p;
}
//只负责释放内存
void operator delete(void *ptr)
{
	cout << "operator delete" << endl;
	free(ptr);
}

//只负责内存开辟
void *operator new[](size_t size)//size_t自己计算size大小
{
	cout << "operator new[]" << endl;
	void *p = nullptr;
	p = malloc(size);
	if(p == nullptr)
		throw bad_alloc();
	return p;
}
//只负责释放内存
void operator delete[](void *ptr)
{
	cout << "operator delete[]" << endl;
	free(ptr);
}
int main()
{
	int *p1 = new int;
	delete p1;

	int *p2 = new int[10];
	delete []p2;
	return 0;
}


我们可以看到代码的运行结果中,每次会调用相应的重载函数。而new和delete的重载函数实现则是依赖malloc和free实现的。只是new和delete,不管是申请内存还是释放内存,会因为分配对象的内存是一个字节的指针还是一个数组(多个字节)来调用不同的函数。所以new和delete用于申请和释放一个字节的内存,而new []和delete []则用于申请和释放多个字节的内存。但是如果不匹配使用,会造成什么问题呢?

int main()
{
    int *p = new int[2];
   //delete []p;    ok
    delete p;
}

如果程序申请的是8个字节的内存,如果执行delete p,则会引起程序崩溃。

原理如下:

如果用new申请一段内存时,内存会自动在首部分配4个字节的空间,用来存储给多少对象分配了空间,也就是存储对象的个数。而真正申请到予以使用的内存空间则会在这四个字节之后。

拿上面例子来说,p申请到的是以0x104为起始地址的8个字节的内存,但是会在其首部分配4个字节的空间用来存储对象的个数。如果释放内存的时候,执行 delete []p; 编译器在见到[]运算符的时候,会自动在p的真实地址0x104的基础上减4的到0x100,然后先释放p数组内每个元素的内存(0x104、0x108),然后释放数组的内存(0x100)。

但是如果执行 delete p; 编译器认为p只是一个字节(0x104 - 0x108)的对象,在new的时候不会给它分配4个字节的内存用来存储对象的个数。所以它直接从0x104释放内存。但是p真实的内存空间有12个字节,所以引起程序崩溃。

猜你喜欢

转载自blog.csdn.net/Disremembrance/article/details/89094593