C++中的new, delete, new[]和delete[]

1. new和new[], delete和delete[]

首先,我们引入一个平凡析构函数的概念,所谓平凡,就是析构函数调用与不调用并没有区别,也就是析构函数是空的。一个类的析构函数是平凡的,需要满足以下条件:

  • 我们没有显式定义析构函数(如果我们定义了,编译器会认为析构函数是必须调用的,尽管我们自定义的析构函数是空的)
  • 析构函数不是虚函数
  • 类类型非静态成员变量的析构函数也是平凡的
  • 基类的析构函数也是平凡的

那么析构函数平不平凡与动态内存的申请和释放又有什么关联呢?先简单解答一下这个问题,如果析构函数是平凡的,那么我们使用MyClass *p = new MyClass[3];时,在堆上只会分配大小为sizeof(MyClass) * 3的内存;如果析构函数是不平凡的,在堆上分配的内存大小为sizeof(unsigned long) + sizeof(MyClass) * 3,就是在实际存在对象的内存会前,会用8个字节记录申请的对象个数,当使用delete[]释放内存时,会往前读取8个字节,以确定要调用的析构函数次数。
注:测试过程在linux系统上进行,可能windows系统只需要4个字节用于记录个数。
我们先定义一个类,这个类中我们显式定义了析构函数,尽管这个析构函数没有任何代码,但是编译器仍把这个类认为为是一个不平凡的类。

class MyClass {
private:
    int a;
    int b;
public:
    MyClass() {
        this->a = 10;
        this->b = 5;
        cout << "无参构造" << endl;
    };
    ~MyClass() {
        // cout << "析构函数" << endl; 
    }
};

接下来实现一下测试代码

int main() {
	
	// 获取一个MyClass对象的占用的字节数
    cout << "sizeof(MyClass) = " << sizeof(MyClass) << endl;
	
	// 堆上申请3个MyClass对象
    MyClass *p = new MyClass[3];
    int tmp1 = 1;
    float tmp2 = 2.0f;
    double tmp3 = 3.0;
    bool tmp4 = true;
    short tmp5 = 4;

    printf("&p = %p, sizeof(p) = %ld\n", &p, sizeof(p));
    printf("(p - 1) = %p, *(p - 1) = %ld\n", (p - 1), *((unsigned long *)(p - 1)));
    printf("&p[0] = %p, (p + 0) = %p, sizeof(p[0]) = %ld\n", &p[0], (p + 0), sizeof(p[0]));
    printf("&p[1] = %p, (p + 1) = %p, sizeof(p[1]) = %ld\n", &p[1], (p + 1), sizeof(p[1]));
    printf("&p[2] = %p, (p + 2) = %p, sizeof(p[2]) = %ld\n", &p[2], (p + 2), sizeof(p[2]));
    printf("第一个对象p[0]的地址 %p\n", &p[0]);
    printf("第一个对象p[0]的地址 %p\n", &p[0]);
    printf("&tmp1 = %p, sizeof(tmp1) = %ld\n", &tmp1, sizeof(tmp1));
    printf("&tmp2 = %p, sizeof(tmp2) = %ld\n", &tmp2, sizeof(tmp2));
    printf("&tmp3 = %p, sizeof(tmp3) = %ld\n", &tmp3, sizeof(tmp3));
    printf("&tmp4 = %p, sizeof(tmp4) = %ld\n", &tmp4, sizeof(tmp4));
    printf("&tmp5 = %p, sizeof(tmp5) = %ld\n", &tmp5, sizeof(tmp5));
    
    return 0;
}

输出结果为

sizeof(MyClass) = 8
无参构造
无参构造
无参构造
&p = 0x7ffe00f2d260, sizeof(p) = 8
(p - 1) = 0x5c42792722c0, *(p - 1) = 3
&p[0] = 0x5c42792722c8, (p + 0) = 0x5c42792722c8, sizeof(p[0]) = 8
&p[1] = 0x5c42792722d0, (p + 1) = 0x5c42792722d0, sizeof(p[1]) = 8
&p[2] = 0x5c42792722d8, (p + 2) = 0x5c42792722d8, sizeof(p[2]) = 8
第一个对象p[0]的地址 0x5c42792722c8
第一个对象p[0]的地址 0x5c42792722c8
&tmp1 = 0x7ffe00f2d258, sizeof(tmp1) = 4
&tmp2 = 0x7ffe00f2d25c, sizeof(tmp2) = 4
&tmp3 = 0x7ffe00f2d268, sizeof(tmp3) = 8
&tmp4 = 0x7ffe00f2d255, sizeof(tmp4) = 1
&tmp5 = 0x7ffe00f2d256, sizeof(tmp5) = 2

直接看上面的输出结果,可能比较难以理解,根据上面的输出结果,我绘制了如下的堆栈信息图。
在这里插入图片描述
指针p指向的是第一个MyClass对象的地址,我们通过往前移动8个字节,并以unsigned long的类型输出其中的内容,确实是为3,这也就验证了我们前面的说法,对于不平凡的类,new[]会多使用8个字节记录对象的个数。
从上图中,我们还可以发现,栈中变量存储的顺序并不是我们定义的顺序,而是占用空间较大的变量会被存储在栈底,占用空间较小的变量会被存储在栈顶(栈的增长方向是从上往下)。
接下来,我们需要判断对于不平凡的类,是否会记录对象的个数。我们修改一下MyClass的定义,修改结果如下。

class MyClass {
public:
    int a;
    int b;
public:
    // MyClass() = default;
    MyClass() {
        this->a = 10;
        this->b = 5;
        cout << "无参构造" << endl;
    };

    // 不显示声明析构函数
    // ~MyClass() {
        // cout << "析构函数" << endl; 
    // }
};

不改变测试代码,运行结果为

sizeof(MyClass) = 8
无参构造
无参构造
无参构造
&p = 0x7fff57894ef8, sizeof(p) = 8
(p - 1) = 0x5786ba7e02b8, *(p - 1) = 33 # 输出的值不再是对象的个数
&p[0] = 0x5786ba7e02c0, (p + 0) = 0x5786ba7e02c0, sizeof(p[0]) = 8
&p[1] = 0x5786ba7e02c8, (p + 1) = 0x5786ba7e02c8, sizeof(p[1]) = 8
&p[2] = 0x5786ba7e02d0, (p + 2) = 0x5786ba7e02d0, sizeof(p[2]) = 8
第一个对象p[0]的地址 0x5786ba7e02c0
第一个对象p[0]的地址 0x5786ba7e02c0
&tmp1 = 0x7fff57894ef0, sizeof(tmp1) = 4
&tmp2 = 0x7fff57894ef4, sizeof(tmp2) = 4
&tmp3 = 0x7fff57894f00, sizeof(tmp3) = 8
&tmp4 = 0x7fff57894eed, sizeof(tmp4) = 1
&tmp5 = 0x7fff57894eee, sizeof(tmp5) = 2

我们发现第6行的输出结果不再是对象的个数。
对于基本数据类型以及平凡的类,使用new[]或new申请的内存,使用delete[]或delete释放并没有区别。但对于不平凡的类,使用new[]申请内存时,会在第一个元素的前面记录元素的个数,当使用delete[]释放内存时,会先读取元素的个数,并调用对应次数的析构函数,最后释放内存;而使用delete释放内存时,只会调用一次析构函数(如果需要),最后释放内存。

根据个人测试结果推测,内容可能存在错误!!! 若有错误,感谢指正~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

it00zyq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值