C++内存管理(1):new与delete

本系列内容总结自侯捷老师的内存管理课程,主要针对STL标准库中分配器的实现以及vc6中malloc的实现。对深入理解内存管理有很大帮助。

前言

我们在分配内存时,经常使用new和delete,但却经常“知其然不知其所以然”。

其实这一切的实现,都是C++设计者对于内存管理的精妙设计。

如侯捷老师常说,“源码之前,了无秘密”。

C++内存管理的源码剖析是了解底层实现的最好途径。

这里就对侯捷老师的C++内存管理这门课程做一些知识性的总结。

因为设计结构非常精妙,所以文中会引用课程的ppt作为解析参考,这些ppt所阐述的内容非常丰富,值得我们好好研究学习。

纵观

想要更好地掌握细节,就要先从宏观入手,明白内存分配的来龙去脉。

我们在使用C++应用程序时,会有这么几种情况:

  • 调用库中的分配器分配内存(比如STL中的allcoator)
  • 使用new与delete分配内存
  • 使用malloc和free分配内存
  • 使用系统API分配内存

这些情况有以下关系:

image-20220526165932876

我们可以看到它们之间的递进关系。

库中的分配器其实是基于new和delete实现的。

而new和delete则是基于malloc和free实现的。

malloc和free则是基于系统API实现的。

这么说来,好像只要明白malloc和free就明白了所有的机制。其实不然,这种彼此之间的依赖关系只是精妙实现的基础。

new与delete

new

首先从最熟悉的new开始。我们都知道new是c++的表达式,用于分配堆内存。

假设我们有一个复数类,当我们执行

Complex* pc= new Complex(1,2);

new到底发生了什么呢?

其实这其中执行了三步:
image-20220526170856140

可以看到第一步调用了operator_new()分配了内存。

然后将这块内存的类型进行转换,转换为Complex类型。

最后调用Complex的构造函数,这样这块Complex类型的内存就分配好了。

(operator_new()其实调用了malloc(),可以理解为直接用malloc分配了内存。)

当然,我们不可以直接用指针调用构造函数,除非使用placement new(后面说)

delete

有new就要有delete,那么当我们执行

delete pc;

会发生什么呢?

其实delete也分为两步:

image-20220526171512166

先对Complex对象进行析构,再operator_delete()这块内存。

(operator_delete()调用了free(),也就是用free()释放内存)

array new与array delete

new和delete最基本的操作就是需要配套使用,new[]和[]delete也需要配套,否则会出现内存泄漏。

到底是哪里泄漏了呢?

我们就来看看这两组方法的区别。

基本数据类型

对于基本数据类型来说,比如int

int *a=new int[10];

使用两种方法delete

delete a;
delete[] a;

其实这种情况并没有内存泄漏。两种方式都可以正确的释放。

因为分配基本数据类型时,系统可以记忆分配的内存大小,析构时并不会调用析构函数。

通过指针可以直接获取到实际分配的内存空间。

没有指针成员的类

如果是一个没有指针成员的类,比如:

class Complex{
public:
	Complex(){}
	~Complex(){}
private:
	int a;
	int b;
}
Complex* pc=new Complex[3];

那么这时使用

delete pc;
delete[] pc;

会有区别么?

区别是有的,但这种细微的差异在这种情况下不会导致内存泄漏。

我们在构造时会调用三次构造函数,而使用delete pc却只会调用一次析构函数。

但使用delete pc会释放pc指针对应的内存空间,所以不论使用哪种方法,我们申请的三块内存都会被释放。

如果使用delete[] pc就是下图的实现方式。

image-20220526174635759

所以对于这样的类来说,不论调用几次析构函数,都不会引起内存泄漏。

含有指针成员的类

其实真正的内存泄漏发生在这样的类中。

class string{
public:
	...
private:
	char* charaters;
	int len;
}

可以看到string类中有指针成员,指向一块地址空间。

如果执行

string* psa=new string[3];

这时使用delete和delete[]就会有很大的区别。

image-20220526175223163

如果我们使用delete psa,那么只会调用一次析构,而实际上还有两块空间需要析构才可以释放,就会产生内存泄漏

所以在这种含有指针成员的类中,对应new[]必须要使用delete[]才能保证释放完整,不产生内存泄漏。

最好的方法就是不论在什么情形下,都使得new和delete,new[]和delete[]配对。

空间分配一览

对于new这个操作分配的空间,大概是这样的。

int *pi= new int[10];

image-20220526200715052

这就是malloc分配的空间

我们得到的是00441c30这个地址,而实际大小因为加上了一些附加值要大得多。

malloc为什么这样分配,我们后面继续探究。

因为int是基本数据类型,所以系统可以记忆分配了多少空间。如果分配的是自定义类型,那么要多一点空间。

假设一个Demo类中有3个int成员

Demo *p=new Demo[3];

在这里插入图片描述

这里布局不同,多了一个用于记录分配长度的3,我们得到的指针是00481c34,也就是黑色的指针。

而调用delete[]p后,p指向的是00481c30,也就是红色的指针,这样可以读取到有多少个对象分配了。

如果在栈上分配,那么大小应该就是4字节(int)*3*3=36

但是堆上会分配61h,还是有较大差距的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值