写在前面
好久没认认真真写博客了, 最近一直忙着实习, 写的一些东西都发在内网了.
这次来总结一下 C++中动态数组的释放, 实际上有两种情况, 针对不同的情况也有不同的处理方式, 当然最保险的还是对应, 即:
- new对应 delete
- new[]对应 delete[]
最为保险, 但是昨天看了一个笔试题, 竟然多选, 那么就可以好好思考一下了.
下面分两种情况来分析, 即:
- 栈内存容器(complex 模板类)以及 POD 类型, 即 C-style 的类型, 例如 int, float 等等
- 堆内存容器(vector 这种动态数组肯定是堆内存), 以及用户自定义类型.
情况 1: 栈内存/POD
POD 类型
来看下面的代码:
#include <bits/stdc++.h>
using namespace std;
void t1() {
int* p = new int[4];
// 赋值
for (int i{}; i < 4; ++i) {
p[i] = i + 1;
}
printf("addr: %p\n", &p);
for (int i{}; i < 4; ++i) {
printf("%d\n", *(p + i));
}
delete p;
printf("addr: %p\n", &p);
for (int i{}; i < 4; ++i) {
printf("%d\n", *(p + i));
}
// addr: 0x16bb2abf8
// 1
// 2
// 3
// 4
// addr: 0x16bb2abf8
// 1273102416
// 12287
// 2043
// 0
}
首先分配 new 动态数组, 在一次delete
之后并没有发生内存泄漏, 而是全部重置了.
栈内存容器
然后就是 complex 类模板, 这个也是一样的道理:
void t2() {
using namespace std::complex_literals;
complex<int>* p = new complex<int>[3];
p[0] = 1i;
p[1] = 2;
p[2] = 3;
for (int i{}; i < 3; ++i) {
cout << *(p + i) << endl;
}
delete p;
for (int i{}; i < 3; ++i) {
cout << *(p + i) << endl;
}
// (0,1)
// (2,0)
// (3,0)
// (-1825926720,37415)
// (2043,0)
// (2700593,0)
}
如果不加 ASAN 事实上不会报错的. warning 一堆.
情况 2: 动态扩容容器和自定义类型
vector 动态扩容数组
void t21() {
vector<int>* p = new vector<int>[3];
p[0] = vector<int>{1, 2};
p[1] = vector<int>{1, 2};
p[2] = vector<int>{1, 2};
for (int i{}; i < 3; ++i) {
for (auto j : p[i]) {
cout << j << " ";
}
cout << endl;
}
printf("%p\n", p);
delete p;
printf("%p\n", p);
for (int i{}; i < 3; ++i) {
for (auto j : p[i]) {
cout << j << " ";
}
cout << endl;
}
// 1 2
// 1 2
// 1 2
// 0x600002818130
// a.out(28303,0x1f8a75e00) malloc: *** error for object 0x600002818130:
// pointer being freed was not allocated a.out(28303,0x1f8a75e00) malloc:
// *** set a breakpoint in malloc_error_break to debug zsh: abort ./a.out
}
直接爆了, 并且提示是指针还未分配就被释放了, 这个问题主要是因为vector 内部已经分配了堆内存, 然后在外面又是动态数组, 那么 delete 时候就会对这样的指针释放两次, 就会导致这个问题. 要解决这个问题最好直接用二维数组(vector<vector<int>>
)这个数据结构, 而不是裸内存的动态数组, 并不安全.
自定义类型
另一种就是经典的自定义类型了:
class P {
int i{};
public:
P() { cout << "P()\n"; }
P(int x) : i(x) { cout << "P(int)\n"; }
P(const P&) { cout << "P(const P&)\n"; }
operator int() { // for cast
return i;
}
~P() { cout << "~P()\n"; }
};
void t22() {
P* p = new P[3];
p[0] = P(0);
p[1] = P(1);
p[2] = P(2);
for (int i{}; i < 3; ++i) cout << p[i] << endl;
delete p;
for (int i{}; i < 3; ++i) cout << p[i] << endl;
// P()
// P()
// P()
// P(int)
// ~P()
// P(int)
// ~P()
// P(int)
// ~P()
// 0
// 1
// 2
// ~P()
// array-new-delete-all-case.out(54082,0x1f8a75e00) malloc: *** error for
// object 0x6000033e8008: pointer being freed was not allocated
// array-new-delete-all-case.out(54082,0x1f8a75e00) malloc: *** set a
// breakpoint in malloc_error_break to debug
}
报错也是一样的, 注意这里析构函数仅调用了一次, 存在内存泄漏.
结论
对于情况 1, 可以使用delete p;
或者delete[] p;
对于情况 2, 只能使用delete[] p;