C++中的动态内存管理(new和malloc)

在C语言中,用malloc/calloc/realloc/free来进行动态内存管理。

这三者都是在堆上对空间进行开辟,free将申请到的空间释放掉。

malloc只有一个参数,就是需要申请空间的大小,成功返回所开辟空间的指针,失败返回NULL。

void* malloc(size_t size);

calloc有两个参数,第一个参数num表示开辟多少个空间,第二个参数size表示每个空间的大小。所开辟的空间从整体上看是连续的,总共大小为size*num个字节。成功返回所开辟空间的指针,失败返回NULL。与malloc不同的是,calloc会将所开辟的空间默认初始化为0。

void* calloc(size_t num, size_t size);

realloc函数是用来调整已经申请好的空间大小的,因为在对空间的管理过程中,可能对内存大小的要求会有所变动。所以realloc用来完成此功能。realloc也有两个参数,第一个为指向所要调整空间的指针,第二个参数为想要调整的大小。

void* realloc(void* ptr, size_t size);

realloc函数总共完成了三件事情,首先判断旧空间后的大小是否能够满足对新空间的要求,如果满足,直接开辟,然后返回的指针与原本的指针指向同一地址,但是可访问的范围发生了变化。如果失败,返回NULL。

如果旧空间后的范围不足以支持新空间的开辟,那么realloc函数就会重新选择一块空间进行开辟,然后将原本空间内的数据拷贝至新空间,并且释放旧空间。返回的指针为新空间的首地址。

这里写图片描述

用以上三种方式开辟的空间,必须由程序员手动用free函数进行释放,否则就会引发内存泄露。在free掉所开辟空间之后,记得将指向该空间的指针赋空,否则可能会造成野指针问题。

野指针:指向一块空间的地址,但是没有该空间的访问权限。

所以在编写程序的时候,养成好习惯,申请的同时就把释放的操作同步写上去,以防发生内存泄露。

以上的所有都是在C语言中进行空间开辟的方式,但在C++中引入了新的空间开辟的方式,叫做new。而释放空间的方式,叫做delete。

与malloc/free等函数不同的是,new/delete在C++中已经不是一个函数了,而是一个操作符。

相同的是,他们都是动态管理内存的入口,程序员可以通过这些方式来对内存进行管理。

那么既然已经有了malloc/free这样的函数,那么C++中为什么还要引入new/delete这样的管理内存的方式呢?

这是因为malloc无法初始化函数,就算调用calloc也只能把空间的初始化为0。

但用new开辟出来的空间是可以进行初始化的。

    int *p = new int(5);
    delete p;

就像这样,p所指向的空间在最开始就被初始化为5了。但如果仅仅是这样,用new操作符的意义好像并不大。

事实上,我们用new更多的是用来开辟自定义类型的空间,new操作符可以自动计算所要开辟空间的大小,返回对应类型的指针,并且在开辟好空间之后,在所开辟的空间上执行构造函数,用来初始化所开辟空间的数据。所以在使用上,new相对更安全一些。

而delete也并不仅仅是释放空间,在释放空间之前,还会进行自动调用析构函数,对变量内部的内容进行清理,最后释放空间。

如果需要开辟较大的连续空间,在C++中还提供了一种方式,new type[num]这种方式去开辟较大的连续空间。

int *ptr = new int [10];

delete[] ptr;

用new[]开辟的空间必须要用delete[]释放,否则程序有可能会崩溃,或者发生内存泄露等问题。

在使用new的时候,底层会调用operator new()函数来进行开辟空间,operator new()函数会调用malloc()函数。然后在开辟好的空间上执行构造函数,new只会执行一次构造函数。

在使用new[]的时候,底层会调用operator new函数来进行开辟空间,然后operator new函数会调用operator new()函数,operator new()函数会调用malloc()函数。然后在开辟好的空间上执行构造函数,new[]会执行多次,直到构造出[]内参数个的对象。

delete[]和delete也会执行类似的操作,先执行析构函数,然后delete[]调用operator delete函数,operator delete会调用operator delete()函数,最后调用free()函数。

流程图大致如下:

这里写图片描述

除了new和operator new()之外,C++中还有一个东西叫做定位new表达式。

定位new表达式是在已分配的原始空间中调用构造函数初始化一个对象。

也就是说,定位new表达式是没有开辟空间的能力的。

class A{
public:
    A(int a = 10){
        _a = a;
        cout << "A" << endl;
    }
    ~A()
    {
        cout << "~A" << endl;
    }
private:
    int _a;
};

int main()
{
    A* ptr = new A[10];
    new(ptr) A(10);
    delete[] ptr;
    return 0;
}

定位new会在原有的空间上执行构造函数。new()括号中的内容必须为指针,类型后的括号内为初始化列表。

猜你喜欢

转载自blog.csdn.net/zym1348010959/article/details/81141394