C++动态内存管理

new和delete

回想我们C语言时期是如何实现动态内存管理的?是不是靠着malloc、calloc和realloc这三个函数实现的。也许对于用习惯的读者来说,这些代码用的很顺手。不过接下来欣赏一段c++用关键字new和delete实现的动态内存管理,也许你很快就会改变心意:

int main()
{
	int* ptr1 = new int(5);
	int* ptr2 = new int[10] {1, 2, 3, 4};
	delete ptr1;
	delete[] ptr2;
	return 0;
}

上面代码中ptr1申请了一个int空间并且初始化为5;ptr2则申请了10个int空间,前4个初始化为1,2,3,4剩下的初始化为0.
再看看用malloc实现方式:

int main()
{
	int* ptr1 = (int*)malloc(sizeof(int));
	if (!ptr1)
	{
		perror("malloc fail");
		return -1;
	}
	*ptr1 = 5;
	int* ptr2 = (int*)malloc(sizeof(int) * 10);
	if (!ptr2)
	{
		perror("malloc fail");
		return -1;
	}
	free(ptr1);
	free(ptr2);
	return 0;
}

是不是显得十分繁琐,并且ptr2初始化会十分麻烦,这里就没有实现了。
事实上new的优势还远不止于此,对于内置类型会初始化"()"中的值,对于自定义类型则会调用构造函数
在释放空间方面倒是和free相差无几,new开辟的空间用delete释放,new []开辟的空间用delete[]释放。
以上只是粗谈了new和malloc的区别,现在我们详细对比以下他们之间的不同:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

operator new和operator delete

看到operator读者是不是不自觉想到了操作符重载,事实上这里并不是操作符重载。operator new二号operator delete是两个全局函数,new的底层是通过调用operator new实现的,delete的底层是通过operator delete实现的。事实上,我们也可以直接调用这两个全局函数:

int main()
{
	int*ptr=(int*)operator new(10*sizeof(int));
	operator delete(ptr);
	return 0;
}

大家观察上述代码是不是觉得和malloc和free有点相似呢?
这种直觉是正确的,operator new和operator delete的底层都是通过malloc和free实现的。
不过直接调用operator new就像直接调用malloc一样了,不仅需要强转而且不能初始化
另外,如果是自定义类型那么就不会调用构造函数和析构函数。
因此,我们需要手动调用构造函数和析构函数。
其中,手动调用构造函数涉及到定位new的知识:

class Test
{
public:
	Test(int n = 4, int val = 0)
		:_a(new int[n])
		, _val(val)
	{

	}
	~Test()
	{
		delete[] _a;
	}
private:
	int* _a;
	int _val;
};
int main()
{
	Test* ptr = (Test*)operator new(sizeof(Test));
	new(ptr)Test();
	ptr->~Test();
	operator delete(ptr);
	return 0;
}
  • 借助定位new的功能,我们可以手动调用构造函数。
  • 由上述代码可知,在delete自定义类型的时候,也是先调用析构函数,再operator delete这段空间。防止内存泄漏。
  • 大多数情况下我们都是直接调用new和delete的,而不会去使用operator new和operator delete,除非是像内存那种情况。

由此我们可以得出new、delete、new[]和deleta[]的原理:
new的原理

  1. 调用operator new函数申请空间
  2. 在申请的空间上执行构造函数,完成对象的构造

delete的原理

  1. 在空间上执行析构函数,完成对象中资源的清理工作
  2. 调用operator delete函数释放对象的空间

new T[N]的原理

  1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  2. 在申请的空间上执行N次构造函数

delete[]的原理

  1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

那么这时我们又有一个问题:new[]多个自定义类型时,delete[]怎么知道要调用多少次析构函数呢?
事实上,对于有析构函数的自定义类型,new[]多个空间时,会多开辟一个整型空间在前面记录要调用析构函数的次数。因此delete[]会将前面那个空间也free掉,而用delete释放空间就会导致从中间开始free空间而导致报错。
说这么多也只是想强调规范写代码,new对应delete,new[]对应delete[],这样的报错就不可能存在。

相关推荐

  1. C++动态内存管理

    2024-03-25 01:14:04       18 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-25 01:14:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-25 01:14:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-25 01:14:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-25 01:14:04       20 阅读

热门阅读

  1. 蓝桥杯day10刷题日记

    2024-03-25 01:14:04       21 阅读
  2. 微信小程序图片资源优化实践

    2024-03-25 01:14:04       21 阅读
  3. conda删除虚拟环境

    2024-03-25 01:14:04       18 阅读
  4. 什么是高防服务器?

    2024-03-25 01:14:04       19 阅读
  5. duckdb学习-1

    2024-03-25 01:14:04       21 阅读