C++动态内存管理:new和delete

本文目标

  1. 简单了解malloc和new的用法
  2. new和malloc的区别和联系
  3. new和delete的原理
  4. 为什么在C++中用new不用malloc
  5. 探寻new[]和delete[]的原理

new和malloc的用法

C中提供了malloc、realloc和calloc来动态管理内存,通过free来释放内存
这几个函数的用法如下:

int main()
{
    int size =100;
    int* arr1 = (int*)malloc(size * sizeof(int));//申请size个int的空间
    int* arr2 = (int*)calloc(size, sizeof(int));//申请size个int的空间并置0
    arr1 = (int*)realloc(arr1, size * 2 *sizeof(int));//重新给arr1申请2倍的size的int空间,原来空间的内容依次拷贝到新空间中
    free(arr1);
    arr1 = NULL:
    free(arr2);
    arr2 = NULL;//释放后置空指针是个好习惯,但是这里没有一定要置空
    return 0;
}

在C++中则提供了new和delete,具体用法如下:

int main()
{
    int* a = new int;//开一个int的空间
    int* b = new int(10);//开一个int空间并初始化为10
    int* c = new int[10];//开10个int的空间

    delete a;
    delete b;
    delete[] c;
}

需要特别注意的是malloc和free,new和delete, new[]和delete[]一定要配合使用,不能混搭,混搭可能会有很严重的后果(并不是每次都会错误),那就是内存泄漏,所以千万不敢混搭

malloc和new的区别和联系

这个问题是面试的高频考点,曾经刚学习C语言的时候就看到过这个问题,不过老忘,现在再次总结一下。

  1. new和malloc均是在堆上开辟空间。
  2. new和delete是C++操作符,malloc和free是C/C++的标准库函数
    3.而new在开空间之后调用构造函数,delete在删除空间前会调用析构函数, malloc和free只负责开辟和删除空间。(后面讲)
  3. malloc/free需要手动计算开的大小,而new不需要,会自动计算开辟的空间
  4. new失败了抛异常,malloc失败了返回0

接下来重点来了,这个联系不能在这一笔带过,结合第三部分探索new的原理一起讲

new和delete的原理

这部分的学习依旧要用汇编,不过我们只需要简单看懂就可以了。
先说结论,new调用了operator new,operator new调用了malloc

首先简单看一下要用的代码,很简单的

int main()
{
    int* ia = new int;
    delete ia;
    return 0;
}

接下来就是执行代码的时候,本来想看一下new进去后是啥样,直接执行居然进不去,所以通过汇编代码进去看看是啥样
首先new调用了operator new
new int
进去operator new还要跳一次,蛋疼
new int
终于跳到真正的operator new
operator new
我们通过汇编代码终于发现operator new其实也是调了malloc来开空间,但是new失败了抛异常在哪,往下翻
抛异常
扔一个什么标准bad_alloc,嗯没错了,这就是抛异常,至于原理,还没学,下次学了再告诉你们怎么抛

通过上面的汇编代码我们发现,new和malloc的关系非常密切啊,new就用了malloc,然后看看delete是不是也是这样

首先是调用operator delete
delete int
然后,进入operator delete看看
free
看来是调了free没错了,也就是说new和delete与malloc和free其实是密切联系的,但是为什么我们在C++中不直接用malloc和free呢???

为什么在C++中用new不用malloc

为了解决这个问题,我们要从自定义类型入手,也就是类,C++中类的概念很重要,对于类来说,不能简单的开辟空间。
举个例子,如果一个类是管理字符串的,就不是简单地开空间了,我们看图
new
怎么证明new调用了构造函数,简单,写个实例测一下就好了

光说不练假把式,按照国际惯例,先上代码:

#include <iostream>

using namespace std;

class String
{
public:
    String(const char* str = "")
    {
        //构造函数
        int size = strlen(str);
        _str = new char[size + 1];
        _capacity = size + 1;
        char* cur = _str;
        while (*cur++ = *str);
    }

    ~String()
    {
        //析构函数
        delete[] _str;
    }

private:
    char* _str;
    int _size;
    int _capacity;
};

int main()
{
    String* s1 = (String*)malloc(sizeof(String));
    String* s2 = new String;//VS下运行代码,执行到此步按F11
    free(s1);
    delete s2;
    return 0;
}

main函数中new对象String那句按F11,发现直接出现下图情况
new调用构造函数
看下执行完毕结果
new和maloc区别
调用构造函数,然后开空间,也就是说new不但开了空间还调用了构造函数,而由于成员是私有的,也就是说malloc开空间之后无法再去给_str开空间,这也就是为什么我们不用malloc而用new的原因。

delete则是先调用析构函数,再进行free,如果free(s2),会出现内存泄漏,这是非常可怕的一件事。(截图就不截了,懒)

探寻new[]和delete[]的原理

最后一个问题,首先看代码

String* s = new String[100];
delete[] s;

问题是:为什么new String[100]的时候给了大小,释放的时候delete[]却不需要给大小,他是怎么知道要释放这么多的空间的?

这个要一句一句执行代码入手了,执行String* s = new String[100];的时候发现一个有趣的东西,看图
new
居然吞了我4个字节,干什么去了,猜测是存储了该指针指向对象的个数,我们把这个四字节的数取出来看看

int* police = (int*)s - 1;//强转一下取出这个4字节的内容

由于修改了代码,分配的空间地址不一样了,这不影响
new
为了证明这不是巧合,我们修改这四个字节的内容,改大一点150,看会不会报错
修改了之后的结果,扔了个异常出来,本来如果不修改这个空间的值他是不会报错的
异常
这也从侧面说明了这个地方存的值就是对象的个数,当delete时,会取出指针前四个字节的内容,知晓删除多少个对象,然后进行删除,至此,案件告破。

因此,这也告诫我们,new[]/delete[]new/deletemalloc/free一定要配套使用,不能瞎搭配,否则后果自负

猜你喜欢

转载自blog.csdn.net/Boring_Wednesday/article/details/80719932