new/delete

举报
无敌清风蓝 发表于 2023/08/31 22:11:57 2023/08/31
【摘要】 new/delete

1. new/delete

1.1.是什么


new/delete C++

malloc/free C

都在堆上

new相比malloc不仅分配内存,还会额外做一些初始化工作

delete相比free不仅释放内存,还会额外做一些清理工作

最明显的区别时什么呢?

class A
{
public:
	A()
	{
		cout << "A()构造函数被调用" << endl;
	}
	~A()
	{
		cout << "~A()析构函数被调用" << endl;
	}
};

int main()
{
	A* p = new A();
	delete p; //A()构造函数被调用
	return 0; //~A()析构函数被调用
}

最明显的区别之一,就是使用new生成一个类对象时系统会调用该类的构造函数,使用delete删除一个类对象时系统会调用构造函数,既然有调用构造和析构函数的能力,意味着new和delete具备针对堆所分配的内存进行初始化(把初始化代码放在类的构造函数里)和释放(把释放相关的代码放在类的析构函数中)的能力,而这些能力是malloc和free所不具备的

1.2.operator new(—)


现在把鼠标放在new上面,会看到这个 operator new(—),放在delete上会看到operator delete(—)

这两个函数和new/delete有什么关系呢

我们知道new运算符做了两件事情

1).分配内存

2).调用构造函数初始化内存

那他是怎么分配内存的呢,其实就是new运算符通过调用operator new(—)来分配内存的,这个函数也可以直接调用的

void * mypoint = operator new(100); //分配100字节内存,一般没人这样做

delete运算符也做了两件事情

1).调用析构函数

2).释放内存

所以释放内存也是靠operator delete(—)的

1.3.new是如何记录分配的内存大小供delete使用


不同编译器的new内部有不同的实现方式

int * p = new int; // 分配出去4字节
delete p;//回收内存的时候,编译器怎么知道要回收4字节,这就是new内部的记录机制,它分配出去多少,他会找地方记录下来,回收的时候就按照这个回收

1.4.申请和释放一个数组

像上面的A类,是0字节的,在C++中,类的大小由其成员变量的大小决定。在这个代码中,类A只有默认构造函数和析构函数,因此它不包含任何成员变量。因此,类A的大小为0字节

class A
{
public:
	A()
	{
		cout << "A()构造函数被调用" << endl;
	}
	~A()
	{
		cout << "~A()析构函数被调用" << endl;
	}
};
int * p = new int[2] //如果不delete的话,泄露8字节
int a = sizeof(A); //即使这个类为空,也有1字节,因为一个类对象肯定有地址,一个内存地址至少能保存1字节
A * pA = new A(); //不delete的话,泄露1字节,这个1字节其实是A对象的大小,由于A类包含构造和析构函数,因此它才占用了内存空间,所以A对象的大小不是0字节,就和买房子一样,不可能买0平米的
A * pA = new A[2](); //不delete的话,看起来应该泄露2字节,但实际是泄露6字节
int * p = new int[2];
delete p;
delete[] p;

这两个释放内存没有区别,delete p和delete[] p的区别在于,delete p会调用一次析构函数,而delete[] p会调用每个成员的析构函数。如果数组类型是自定义类,那么new []只能用delete []来对应,new和delete对应。但是对于普通数据类型而言,他们作用的效果是一样的,例如int* p=new int,delete p和delete [] p作用效果是一样的,原因是内部普通数据类型没有析构函数。123

A * pA = new A[2]();
delete pA; // 报错
delete[] pA; //这是规范的,调用了两次析构函数,因为分配内存时候调用了两次构造

那delete[] pA为什么会调用两次A的析构呢,系统怎么知道new的时候new出来了几个数组元素(类A对象)呢

C++的做法是,在分配数组空间的时候多分配了4字节的大小(也就是上面A * pA = new A2;泄露6字节的原因,也就是为什么多出来了4字节),专门保存数组的大小,在delete[]时就可以取出这个数组大小的数字,因为数字是int,所以是4字节,就知道了需要调用析构函数多少次了

1.5.为什么new/delete new[]/delete[]要配对使用


继续上面的,就是delete p和deletep[] p的问题

记住这个结论,如果一个对象使用new[]分配内存,而用delete释放内存(不是delete[]),那么这个对象满足的条件是:对象的类型是内置类型(比如int类型)或者是无自定义析构函数的类类型

现在把类A的析构函数注释掉,就会发现

A * pA = new A[2]; //这里不再分配6字节,而是2字节
delete pA //不会报异常了

反过来看,如果A有自己的析构函数,用new[]分配,而用delete来释放,就会报错了

那这是为什么呢

因为delete pA做了两个事情

1.调用一次类A的析构函数,new时候创建了两个对象,构造函数了两次,而只释放了一个析构,则就可能出问题了,比如在构造函数中如果分配了内存,希望在析构释放,但是少了一个析构,就内存泄露了

2.调用operator delete(pA);来释放内存,系统报错其实就是执行这个调用导致的,因为多分配的这4字节的问题导致释放的内存空间错乱,比如明明释放一个0x00000012作为开始地址的内存,因为内存空间的错乱,导致释放了0x00000016作为开始地址的内存,从而导致异常

同样如果new而用delete[]也会出错,因为不断调用析构

所以要配对使用

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。