在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()括号中的内容必须为指针,类型后的括号内为初始化列表。