c与c++动态内存管理
首先我们来看看它们都是怎么使用的吧。
malloc
void *malloc( size_t size );
calloc
//num:开辟的这块空间有多少个元素
//size:每个元素的字节数
void *calloc( size_t num, size_t size );
realloc
//memblock需要扩容的指针
//size 需要扩充到n个字节
void *realloc( void *memblock, size_t size );
new
new + 需要申请的类型
delete
delete + 指向需要销毁的那块空间的指针
new []
//申请一块内存,用于存放数组
new + 类型[大小]
delete
delete[] + 指针
在C语言中是使用malloc来申请空间,用free来释放空间
c语言中不止是有malloc还有calloc,realloc,它们都是c语言中用来动态开辟内存的。
那么他们之间有什么区别呢?
- malloc:用来申请一块空间,malloc(需要开开辟的空间的字节数),用malloc开辟的空间,没有初始化,就只是一块空间。
- calloc: 开辟一块空间,并且对这段空间进行初始化。自动将这段空间都初始化为零。
- realloc:在原来的空间上进行扩容。若是当前内存有足够的空间可以开辟,那么就直接在当前内存的后面开辟,若是内存不足,则需要再重新找一块内存,开辟空间,将之前的值再拷贝过来。然后释放之前的内存空间。
如果申请成功,返回的都是当前内存的地址,若是申请失败,则返回NULL。
在不使用这段内存空间后,一定要记得释放这段空间,用free(指向这段空间的指针)。如果不释放,就会造成内存泄漏。表面看着没有什么影响,但是会一直占着内存,直到程序结束。
c++与c语言一样,也有动态内存开辟的机制,支持程序员自己申请与释放内存。
在c++中就是使用new和delete,new是用来申请空间,delete用来释放空间。
既然在c语言中已经有了动态开辟内存的机制,为什么在c++中还要引入new和delete呢?
下面我们就来说一说malloc/free与new/delete的区别
- malloc/free是c/c++中的函数,而new/delete则是操作符。从它们的使用方式就可以看出来。malloc/free使用时需要有参数,参数必须得有括号。而new/delete则不需要用括号,后面直接跟需要创建的类型就可以。
- malloc/free只是负责开辟空间与释放空间,new与delete则是还需要调用构造函数与析构函数。对开辟的空间要进行初始化,若是要销毁,就需要调用析构函数,来清理空间。
- malloc/free需要自己提供需要开辟的字节数,并且返回值为void*,这就需要程序员对接收这块空间的变量类型确定,并且要强转返回的类型为想要的类型,不然就会出错。但是new/delete只需要提供想要开辟的类型即可。而且返回的就是对应类型的指针。不需要再手动强转。
他们的相同点就是都可以申请空间/释放空间。
接下来,我们详细的说明一下它们的用法
new / delete
它们是动态管理对象的。调用构造析构函数。
现在举个例子来验证一下:
#include<iostream>
using namespace std;
class AA
{
public:
AA()
{
cout<<"AA()"<<endl;
}
~AA()
{
cout<<"~AA()"<<endl;
}
};
int main()
{
AA *p1 = new AA;
delete p1;
return 0;
}
从上面的这个例子能看出,new/delete在对自定义类型使用的时候,,会自动的调用它们的构造函数与析构函数。创建一个对象就会调用一次构造函数。销毁一个对象的时候也会调用一次其析构函数。那么new[]/delete[]是怎么调用构造析构函数的呢?
AA *p1 = new AA[3];
delete[] p1;
上面这段程序,创建了一个数组,这个数组时候一个存对象的数组,有三个元素。所以需要对每一个对象调用构造函数,并且清理的时候也需要调用三次析构函数。就出现了上图的结果。
上面介绍的就是new/delete,new[]/delete[]的简单用法。接下来说一说它们的匹配使用问题。
new/delete是一组,new[]/delete[]是一组。一定是要匹配使用,不可以打乱组合。否则可能会出现内存泄漏甚至程序崩溃。从上面说到的来看,new/delete都是只调用一次,new[]/delete[]调用几次取决于数组中的元素个数。所以,如果出现了不匹配使用,那么调用构造函数与析构函数的次数也就不会匹配,那么就会导致程序出问题。这就是不匹配使用的为什么会导致错误的原因。
在c++中还有别的动态内存管理的接口。
- operator new(size_t size)
- operator delete(size_t size)
- operator new[](size_t size)
- operator delete[](size_t size)
operator new/operator delete是一组,匹配使用,负责开辟空间与释放空间。它们的作用相当与c语言中的malloc与free,与它们的用法一样,实际上,operator new/operator delete是malloc/free的一层封装。
operator new[]/operator delete[]是一组,匹配使用,负责开辟连续的数组空间与释放连续的空间。
这四个接口都是函数,不是操作符。看样子是有点像new/delete的运算符重载,其实并不是。new/delete,new[]/delete[]是通过这四个对应的函数来获取内存与释放内存。它们不会调用对象的构造函数与析构函数来初始化对象与清理对象。
具体过程如下所示:
调用构造函数是new操作符完成的,new又调用operator new开辟内存。而在operator new中有个malloc函数,是负责开辟空间。所以c++中的内存管理就是在c语言的基础上进行了封装与修改得到了c++中的内存管理机制。
new[n]是开辟一段连续的空间。这段连续的空间里面存放的是相同的类型数据。new负责调用n次构造函数再调用operator new[],来开辟空间。与new一样,底层实现还是malloc函数。
delete与new类似,delete操作符负责调用一次析构函数,再调用一次operator delete函数,operator delete负责清理空间,operator delete函数是free函数的封装,所以,其实是free释放的空间。
delete[]这个操作符就有些不一样了。因为delete[]不需要写具体数字,那么,我们怎么知道需要调用几次析构函数呢?
其实在new[]时,在开辟的空间前四个字节,存储的是n。即当前这个数组中元素的个数。在需要delete这个数组时,只需向前读四个字节,就可以知道当前数组的个数,即知道需要调用几次析构函数。