【C++】 new delete、new[] delete[] 详解

  C++ 中,new、delete 和 sizeof 一样,都不是函数,都是操作符。面试经常会问 malloc/free 和 new/delete的区别和联系:

  • malloc/free 只是动态分配内存空间/释放空间,new/delete 除了分配空间还会调用构造函数析构函数进行初始化与清理
  • 它们都是动态管理内存的入口
  • malloc/free 是 C/C++ 标准库的函数,new/delete 是 C++ 操作符
  • malloc/free 需要手动计算类型大小且返回值为 void*,new/delete 可自动计算类型的大小返回对应类型的指针
  • malloc/free 管理内存失败会返回 0,new/delete 等的方式管理内存失败会抛出异常
  • 从系统层面看来,真正开出空间来的还是 malloc,operator new 和 operator delete 只是 malloc 和 free 的一层封装
正确写法,new[]、delete[] 配对使用

  先设计一个类,每个对象有自己的 id,这样我们可以看到构造和析构的顺序,之后的所有有 A 这个类的代码都用这个 A:

class A {
public:
	int id;
	explicit A(int _id) : id(_id) { printf("构造 id 为 %d 的对象\n", id); };
	~A() { printf("释放 id 为 %d 的对象\n", id); }
};

  然后是正常的 new[] 构造数组,delete[] 析构数组:

int main() {
	A* pa = new A[3]{ A(1), A(2), A(3) };
	putchar('\n');	// 用来分开构造和析构的输出
	delete[] pa;
}

  上边代码输出如下:

构造 id 为 1 的对象
构造 id 为 2 的对象
构造 id 为 3 的对象

释放 id 为 3 的对象
释放 id 为 2 的对象
释放 id 为 1 的对象

  可以看到这是正常的构造和析构过程,这就引发了两个问题:
  1、delete[] 是怎么知道数组有多大的?
  2、如果不配对,new[] 之后用 delete 会怎么样?
  3、为什么析构顺序是反着的?

一、delete[] 怎么知道数组有多大的?
1.1 C++有析构函数的对象

  还是上边的代码,进入调试,可以看到 operator new[] 返回的确实是 void* 类型,pa 则是 A* 类型。

  不过我们的重点不是这里,而是值,可以看到 operator new[] 返回的值(也就是指针地址)和 pa 的值相差 4,这是因为编译器用这 4 个字节来保存 对象个数,也就是在这个 pa 指针向前 4 个字节中,记录这对象个数这个值,我们用下面代码验证一下,发现对象个数输出确实是 5,甚至还可以手动修改这个值,如下:

int main() {
	A* pa = new A[5]{ A(1), A(2), A(3), A(4), A(5) };
	printf("\n对象个数: %d\n", *(pa - 1));
	*reinterpret_cast<int*>(pa - 1) = 3;	// *((int*)(pa - 1)) = 2; 也行
	delete[] pa;
}

  输出如下:

构造 id 为 1 的对象
构造 id 为 2 的对象
构造 id 为 3 的对象
构造 id 为 4 的对象
构造 id 为 5 的对象

对象个数: 5
释放 id 为 3 的对象
释放 id 为 2 的对象
释放 id 为 1 的对象

  这个输出很重要! 说明 delete[] 确实是根据数组指针前 4 个字节存储的数据作为对象个数的记录的,这就解释了为什么 delete[] 不需要传入参数

1.2 基础类型或者没有析构函数的类型

  对于基础类型,没有析构函数,或者编译器会优化的类型,就不会有这个记录对象个数的值存在

int main() {
	int* a = new int[10];
	printf("\n对象个数: %d\n", *(a - 1));	//  -33686019
	delete[] a;
}

  而且 delete a; 和 delete[] a; 也都不会崩。
  上边的类 A 中,如果把析构函数注释掉,也不会有对象个数的记录,会输出一个随机值。也就是说,对于一个没有析构函数的类的对象的数组,delete[] 不会为每个成员调用析构函数,只需要将指针指向的内存释放就可以了,如果指向的内存释放中出现了内存泄漏,也不是delete[] 的问题,而是你这个类设计的时候没有写析构函数的问题。我的理解是这种情况下,delete 和 delete[] 其实是一样的,没有区别。
  但是无论如何,还是应该保持 new[] 和 delete[] 的配对原则使用。

二、如果不配对使用,new[] 之后用 delete 会怎么样?
int main() {
	A* pa = new A[5]{ A(1), A(2), A(3), A(4), A(5) };
	delete pa;
}

  比如上边这样的代码,运行起来,会输出:

构造 id 为 1 的对象
构造 id 为 2 的对象
构造 id 为 3 的对象
构造 id 为 4 的对象
构造 id 为 5 的对象
释放 id 为 1 的对象

  然后崩掉,提示下图:

  所以可以看到,new[] 构造出对象数组,但是只用 delete 去释放的话,会有两个问题:
  1、只会对第一个对象的调用析构函数,其他对象的内存不会释放
  2、更严重的是,有时候会直接崩掉
  也不可以 new 之后 delete[],总之一句话,new 配 delete,new[] 配 delete[]
  所以面试时,如果问到如果 new[] 了之后 delete,会有什么问题,不能只回答剩下的部分不会被调用析构函数,因为可能还会导致程序崩溃(之前我就是答的内存泄漏这个问题,然后他问我还有什么问题,我就蒙了 = =)。

三、为什么析构顺序是反着的?

  没有看到官方的、靠谱的解释,可能就是 C++ 的规定,很多构造和析构都是先构造的后析构,类似栈的感觉,比如继承关系中:

class A {
public:
	A()          { printf("构造 A 的对象\n"); }
	virtual ~A() { printf("释放 A 的对象\n"); }
};

class B : public A {
public:
	B()  { printf("构造 B 的对象\n"); }
	~B() { printf("释放 B 的对象\n"); }
};

int main() {
	A *p = new B();
	delete p;
}

  输出:

构造 A 的对象
构造 B 的对象
释放 B 的对象
释放 A 的对象

  肯定还是不太一样,如果有大神可以讲解一下,将万分感谢!!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值