new/delete实现的原理

文章探讨了C++中new/delete与malloc/free在内存分配上的相似性和差异,重点讲解了new/delete如何调用构造/析构函数,并揭示了operatornew[]如何封装malloc并处理错误。此外,文章分析了为何new[]会多开4个字节存储元素个数,以及不同内存释放操作的正确使用方法。
摘要由CSDN通过智能技术生成
在堆上申请空间,对于内置类型,new/delete和malloc/free的功能几乎相同,对于自定义类型,new/delete会多做一件事:调用该自定义类型的构造/析构函数。
class Stack
{
public:
	Stack(int capacity=4)
	{
		cout << "Stack()" << endl;
		_a = new int[capacity];
		_size = 0;
		_capacity = capacity;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_a = nullptr;
		_size = 0;
		_capacity = 0;
	}
private:
	int *_a;
	int _size;
	int _capacity;
};

void Test2()
{
	Stack *ps = new Stack[5];
	delete[] ps;
}

除此之外,new/delete和malloc/free的处理错误方式也不同,new/delete开辟空间失败会抛异常,而malloc/free开辟失败会返回一个空指针。
new/delete作为操作符,它本身无法开辟空间,C库里面的malloc函数才能做到开空间,最终开辟空间/释放空间这种事情还是要交给malloc/free。new/delete只是直接或间接封装了malloc/free,并对结果的处理方式做了一些优化:malloc开辟空间失败返回空指针不符合C++面向类和对象的需求,new/delete将它们封装并将处理结果改为抛异常
于是大胆猜测,malloc/free可能被封装到某个中间操作符里面,在这里对malloc开辟空间失败返回空指针的方式进行优化,申请空间的步骤完成,然后在调用析构/构造函数,再将这个中间操作符封装到new/delete里面,这样才是C++面向类和对象的编程。
下面将从汇编的层面理解这个问题:
通过反汇编可以看到,第二行的operator new[]就是封装了malloc的操作符,而它里面是会去调用malloc开辟空间的。
同样,也可以通过反汇编看到new调用了构造函数(这里找起来比较麻烦,也是找了老半天)
现在比较好奇的是operator new[]里面是怎么实现的,只是调用了malloc函数这么简单吗?
进入operator new[]后发现,的确是在开空间,想必是开count个Stack大小的空间,也就是5*12=60个字节的空间
但是结果出人意料,打开监视窗口,它并不是60而是64个字节
为什么会多开4个字节,多开的4个字节里面又存的什么东西?
直接说结论,这四个字节就是存的该类的个数。
细心点可以发现,每次delete释放多个空间的时候都只需要一个[]即可,但后面还是需要调用析构函数,没有告诉次数编译器怎么知道要调用多少次析构函数 为了能正确调用析构函数的次数,保留这个数字是有必要的。
一般来说,申请和释放空间都要配对使用,malloc和free配对,new和delete配对,new[]和delete[]配对。
下面看来看几段有趣的代码:
class A
{
public:
	A(int a=0)
	{
		cout << "A()" << endl;
		_a = a;
	}
	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

void TestA()
{
	A *pa = new A[5];
	delete pa;
}
执行后程序崩溃了,但是简单的内存泄漏并不会导致程序崩溃。
其实这里崩溃的原因是空间释放不完全。
一样的,用new[]开辟空间会在前面多开四个字节来存储元素个数,并且那四个字节也是在堆上面申请的,用delete编译器就会想当然认为前面没有空间要释放,就会导致内存释放不完全。
而用delete[]就相当于你告诉了编译器这是多元素的内存需要释放,那编译器就知道前面还有四个字节空间需要释放。
在来看一种不带析构函数的自定义类型
class A
{
public:
	A(int a = 0)
	{
		cout << "A()" << endl;
		_a = a;
	}
private:
	int _a;
};

void TestA()
{
	A *pa = new A[5];
	delete pa;
}

这段代码却可以成功运行,也没有内存泄漏的问题。原因是自定义类型不带析构函数那么在delete释放空间时不会调用析构函数,那么它在new申请空间时就没必要多申请那四个字节的空间,它都没有析构函数了还存它的析构次数有何意义。

可以明确的说,即使是用free来释放,这里也不会有任何的问题。
一定情况下,delete[],delete,free都可以使用,但这只是少数情况,内存的申请和释放匹配使用不仅可读性更好,也更安全,保险起见,建议申请释放匹配使用。
  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值