C++动态数组的释放问题,delete[]是不是必须的

 
Category: C++

写在前面

好久没认认真真写博客了, 最近一直忙着实习, 写的一些东西都发在内网了.

这次来总结一下 C++中动态数组的释放, 实际上有两种情况, 针对不同的情况也有不同的处理方式, 当然最保险的还是对应, 即:

  • new对应 delete
  • new[]对应 delete[]

最为保险, 但是昨天看了一个笔试题, 竟然多选, 那么就可以好好思考一下了.

下面分两种情况来分析, 即:

  1. 栈内存容器(complex 模板类)以及 POD 类型, 即 C-style 的类型, 例如 int, float 等等
  2. 堆内存容器(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;