C语言动态内存管理
- C语言为我们提供标准库函数malloc,realloc,calloc和free进行动态内存管理,我们可以先观察一下这三个函数的函数原型:
void *malloc( size_t size);
void *realloc( void *memblock,size_tsize);
void *calloc( size_t num,size_tsize);
- 我们通过简单的使用这些函数来简述这三个函数各自的特性:
int main()
{
int* p1 =(int*)malloc(sizeof(int));
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2,sizeof(int)*6);
free(p1);
free(p2);
free(p3);
return 0;
}
- malloc:一个参数(所开辟空间的大小),返回值为void*,需与free配对使用,防止内存泄漏。
- realloc:可用于第一次开辟空间以及调整动态内存,两个参数(所调整空间的起始位置,所调整空间的大小)。注意:realloc必须接收返回值,因为空间有可能开辟失败,返回为空指针。
- calloc:两个参数(开辟空间的个数,每一份空间的大小)。
C++动态内存管理
我们都知道,C++是在C语言的基础上进行继承和发展的,所以在C++中我们同样可以使用标准库函数malloc,realloc,calloc和free进行动态内存管理。但C++还有更好的方法——new运算符。下面我们试一下这种新技术,若想要开辟一个整型的空间,new/delete运算符将为我们找到一个长度正确的内存块,并返回该内存块的地址。然后用指针来接收这个地址,下面是这样的一个示例:
int* p4 = new int; //开辟1个整型的空间
int* p5 = new int(3); //开辟1个整型的空间并初始化为3
int* p6 = new int[3]; //开辟3个整型的空间
delete p4; //new/delete匹配使用
delete p5;
delete[] p6; //new[]/delete[]匹配使用
- 运算符new/delete在使用上比malloc,realloc,calloc方便了很多,但是它们都做了同一件事:动态内存开辟。
- 我们可以思考一件事:我们知道C++是兼容C的,那么已经有C库malloc/free等来动态管理内存,为什么 C++还要定义new/delete运算符来动态管理内存
深入理解C++动态内存管理
- malloc/free和new/delete的区别和联系?
int* p1 =(int*)malloc(sizeof(int));
int* p4 = new int;
- 上面我们提到new在使用上比malloc方便很多,体现在:malloc需要手动计算类型大小且返回值为void*,使用的时候我们需要将它强转成我们需要的类型;new可自己计算类型大小,并返回对应类型的指针。
- 上面所述的内容都是在内置类型的基础上,C++是面向对象的语言,我们所使用的往往是自定义类型,下面我们自定义一个类来分析一下malloc和new其他的区别和联系:
class Array
{
public:
Array(size_t size= 10)
: _size(size)
, _a(0)
{
cout << "Array(size_t size)" << endl;
if (_size> 0)
{
_a = new int[size];
}
}
~Array()
{
cout << "~Array()"<< endl;
if (_a)
{
delete[]_a;
_a = 0;
_size = 0;
}
}
private:
int* _a;
size_t _size;
};
- 我们先看一下malloc:
void Test()
{
Array* p1 = (Array*)malloc(sizeof (Array));
free(p1);
}
- 内存开辟成功,再来看下new会有什么不同:
void Test()
{
Array* p2 = new Array;
delete p2;
}
- 发生了什么?在运算符new动态内存开辟过程中竟然自动调用了构造函数和析构函数,这个过程是怎样发生的呢?
- 通过程序调试,我们发现利用new来开辟空间,然后它会自动调用我们的构造函数进行初始化,然后调用析构函数做清理工作,最后delete释放空间。这又是malloc和new的一个大不同。这很好的体现了C++面向对象的语言的特点。
- 接下来我们调换一下顺序:malloc开出的空间用delete释放、new开出的空间用free释放:
void Test()
{
Array* p1 = (Array*)malloc(sizeof (Array));
delete(p1);
}
- 程序并没有什么问题。malloc并不会调用构造函数,delete调用了析构函数。
void Test()
{
Array* p2 = new Array;
free p2;
}
- 如果用free释放并不会调用析构函数
- 重点来了,问题就出在这—>new调用了构造函数,在构造函数中已经给p2开了空,但是free并不会调用析构函数,所以p2开出的空间没有被释放掉,这就导致了一个很严重的问题:内存泄露。这种情况很危险。
void Test()
{
Array* p4 = new Array[3];
delete[] p4;
}
- new[N]:开辟空间,调用N次构造函数分别初始化每个对象。delete[]调用N次析构函数清理对象,释放空间。
- 问题又来了,这N次是如何来的?程序中我们并没有指定delete[N],这个问题我们在下部分内容中会详述。
C++的其他内存管理接口
void * operator new (size_t size);
void operator delete (void* p);
void * operator new[](size_t size);
void operator delete[] (void* p);
- 标准库函数operator new/operator delete的命名很容易让人混淆,它并不是运算符重载。实际上我们不能重定义new和delete表达式的行为。
- 我们可以试验一下:
Array* p5 = (Array*) new(sizeof(Array));
- 再来看下面一段代码:(我们详细分析一下函数的调用步骤)
void Test1()
{
Array* p1 = new Array;
delete p1;
}
- 详解如图:
- 说明一点:对于内置类型而言,没有构造函数、没有析构函数,那么无论调new还是malloc都没有区别;why?调用malloc就相当于直接去开辟空间,new相当于是—>new会调用operator new、operator new里面又去调用malloc。也就是说对于内置类型而言,如果是new出来的空间,调用malloc去释放和调用delete去释放是一样的,最终都会调用free;
- 但是对于自定义类型就不同了;如果用malloc开辟了空间,free去释放,就不会调用析构函数,没调用析构函数可能就会出现“内存泄露”。
总结一下:
- operator new/operator delete operator new[]/operator delete[] 和 malloc/free⽤法⼀ 样。
- 他们只负责分配空间/释放空间,不会调⽤对象构造函数/析构函数来初始化/清理对象。
实际operator new和operator delete只是malloc和free的⼀层封装。
new: 1. 调⽤operator new分配空间;2. 调⽤构造函数初始化对象。
- delete: 1. 调⽤析构函数清理对象;2. 调⽤operator delete释放空间。
- new[N]: 1. 调⽤operator new分配空间。 2. 调⽤N次构造函数分别初始化每个对象。
- delete[]:1. 调⽤N次析构函数清理对象。2. 调⽤operatordelete释放空间。
再回到我们上一小节所提出的问题上:程序中并没有指定delete[N]. 这N次是如何来的呢?
void Test3()
{
Array* p4 = new Array[10];
delete[] p4;
}
按照我们自定义的类型,这里应该开辟80个字节的空间,为什么是84呢?结合前面的问题猜想一下,多出的这四个字节的空间有可能是用来存放对象个数的,如果真的是这样,那我们上一小节遗留的问题就可以解决了。我们可以验证一下:
为什么要存对象的个数?原因就在于析构要去做一件事:它首先会去调用析构函数,其次会调用operator delete ,调用operator delete的时候根据p的地址在申请空间时多开了4个字节在头上,然后释放的时候也会减去4个字节,传给operator delete,operator delete再传给free。
- 注意:内置类型(譬如int等)不需要调用构造函数和析构函数。而只有只有调用析构函数才会知道要调用多少次(由对象个数确定),才多开4个字节把对象的个数存下来;对于基本类型直接free就可以,压根不用多开4个字节。
- delete[] 析构函数调用细节剖析:
定位new表达式
- 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。用法如下:
new (place_address)
type new (place_address)
type(initializer-list)
//place_address必须是⼀个指针,initializer-list是类型的初始化列表。
void Test()
{
//1.malloc/free + 定位操作符new()/显⽰调⽤析构函数,模拟 new和delete 的⾏为
Array* p1 = (Array*)malloc(sizeof(Array));
new(p1)Array(100);
p1->~Array();
free(p1);
//1.malloc/free + 多次调⽤定位操作符new()/显⽰调⽤析构函数,模拟 new[]和delete[] 的⾏为
Array* p2 = (Array*)malloc(sizeof(Array)* 10);
for(inti = 0; i < 10; ++i)
{
new(p2+ i) Array;
}
for(int i = 0; i < 10; ++i)
{
p2[i].~Array();
}
free(p2);
}