记录c++的malloc/free、new/new[]和delete/delete[]

1.malloc/free和new/delete的区别

malloc/free和new/delete都是用于内存申请和释放的,但是具体区别知多少?

1.1 new/delete是关键字,malloc/free是库函数(需要include头文件才能用),malloc/free更多的是和operator new/operator delete类似

1.2 malloc申请内存需要显示填入大小,new不用,举例:

//malloc/free库函数使用范例
int* ptr = (int) malloc(4);  //malloc返回void* 类型指针,需要强转
free(ptr);

//new/delete关键字使用范例
int* ptr = new int;
delete ptr;

1.3 返回类型不一样,malloc返回的是void*需要进行强转,new返回的是对应类型的指针,无需强转(类型安全的)

1.4 内存分配失败的返回不同,malloc返回NULL,new不会返回,只会抛出异常,如果不捕获异常,会导致程序崩溃

1.5 内存扩容机制不一样,malloc分配的内存空间如果不足了,可以用realloc来扩容,扩容过程是realloc先检查当前内存空间后面是否还有足够的连续空间,如果有就后面继续申请,并返回原来的指针,如果后面没有足够的,就会另外在别的地方申请一片内存空间,并把原来的内容拷贝到新的内存中,返回新的地址指针,new是动态分配内存,不存在扩容操作

2.new和delete背后的机制

在c++中,new和delete随处可见,但是其背后的机制有了解吗?

//示例
class A
{
public:
    A(int v) : var(v){
        fopen_s(&file, "test", "r");
    }
    ~A(){
        fclose(file);
    }
private:
    int var;
    FILE *file;
};

A* pA = new A(5);
delete pA;

如上new一个A的对象,经过了三个步骤

首先,调用operator new(即new操作符,可以被重载来在栈上分配内存)来申请一个堆内存

其次,调用placement new来在申请好的内存上调用构造函数来初始化对象

最后,返回对象指针

delete一个对象指针,经过了两个步骤

首先,调用placement delete来调用析构函数

最后,调用operator delete来释放内存空间

关于new和delete的过程,如下:

new过程:

来源:https://blog.csdn.net/u010732356/article/details/53958293

delete过程:

来源:https://blog.csdn.net/u010732356/article/details/53958293

了解了new/delete背后的机制后,那么你对new[]和delete[]知多少?

new[]和delete[] 是用于分配数组和释放数组的,它们最好成对出现,为什么说最好,而不是一定要?下面来分析

基本数据类型和自定义类型(类、结构体等)的数组在调用delete和delete[]上有些差别,具体体现在:

1.基本数据类型,可以用delete和delelte[]都是正确的

int *ptr = new int[10];  //内存已经确定 
delete ptr;  //方式1
delete[] ptr; //方式2

方式1和方式2都正确,按照前面new/delete背后机制的理解,new10个int,由于没有构造函数和析构函数,在new的时候不会构造10次也不会析构10次,ptr指向的内存块最后释放掉,因此不会有任何问题

2.自定义的数据类型,此时delete和delete[]使用需与new和new[]成对出现

A* pAa = new A[3];
delete pAa;  //方式1
delete[] pAa; //方式2

根据c++基础知识,方式1会有问题,而方式2是正确的,那么为什么呢?

首先我们需要搞明白,new[]这个过程发生了什么,实际上c++在调用new[]生成对象数组时候,会分配一个大小为4个字节的空间来保存数组的长度(比如上例长度是3),在delete[]时候取出数组长度,这样才能知道数组中有多少能被删掉了

具体过程如下:

首先,调用operator new[]来申请内存,注意要多4个字节用于保存数组长度(基本数据类型没有析构,因此没必要多分配4个字节,下面讨论的问题都是基于自定义类型)

其次,调用placement new[]来在已申请的内存上调用对象构造函数初始化对象

最后,返回指向数组首地址的指针,注意指针指向的位置不是从额外分配的4字节开始的

来源:https://blog.csdn.net/u010732356/article/details/53958293

那么delete[]的过程就很明了了

首先,取出内存空间中的前4个字节得到数组长度,依次对数组中对象调用placement delete[](在这里调用析构函数),因为个数是知道的,那么会准确无误的析构掉数组中每一个对象

最后,operator delete[](pAa),根据pAa指向的地址 - 4个字节地址得到要释放的空间的首地址,然后全部释放

来源:https://blog.csdn.net/u010732356/article/details/53958293

搞清楚new[]和delete[]原理后,那么new/delete和new[]/delete[]要成对使用的问题就很简单了

delete pAa;

上面delete做了两件事

首先,调用placement delete 执行pA指向的对象(数组第一个对象)的析构函数,因此会导致其他两个对象没有调用析构,如果对象上持有资源(如文件、端口等)就会造成无法释放等问题,后果严重

其次,调用operator delete来是释放掉pAa指向的内存空间+首地址4个字节

delete[] pAa; //会依次调用析构函数

上面delete[]做了两件事

首先,调用placement delete[] 执行pAa指向的数组中的所有对象析构函数

最后,调用operator delete[]来是释放掉首地址为pAa地址 - 4个字节的地址所有空间

那么new一个对象用delete[] 来释放会有问题吗?

A* ptr = new A;
delete[] ptr;

根据上面的理解,来分析这个过程

new过程:operator new申请一块内存(注意这里不会额外多出4个字节),placement new初始化对象,返回对象的地址指针

delete[]过程:placement delete[]会取当前指针指向地址之前的4个字节空间中的内容来作为数组长度依次调用析构,调用operator delete[] 释放指针指向地址 - 4字节为首地址的内存空间,那么指针地址 - 4个字节的内存空间中存的数据是未知的,可能会造成严重的问题

转载至:https://zhuanlan.zhihu.com/p/432826184

猜你喜欢

转载自blog.csdn.net/qq18218628646/article/details/132475738