1.c语言的动态内存管理
- malloc:动态开辟指定大小的空间,返回值是void* ,所以要自己指定返回的数据类型
- int *ptr = (int*) malloc (10*sizeof(int));
- calloc:动态开辟指定大小的空间,与malloc不同的是它会进行初始化
- int* ptr = (int*)calloc(10,sizeof(int));
- realloc: 为动态开辟的空间调整大小,如果原空间后有足够大的空间,之间在后面开辟。如果原空间后的空间不够大,它会开辟一块空间,将原来的数据拷贝过来,并释放原来的空间
int *ptr = (int*) malloc (10*sizeof(int));
realloc(ptr,100*sizeof(int));
- free:动态开辟的空间一定要记得释放,否则可能导致内存泄漏(指针丢了,内存还在)
动态开辟的内存都在堆上,堆上的空间由程序员手动开辟,手动释放。
2.c++的动态内存管理:
c++通过new/delete动态管理内存
- new/delete动态管理对象
- new[]/delete[]动态管理对象数组
- new做了俩件事:
- 调用operator new分配空间
- 调用了构造函数初始化对象
- delete做了俩件事
- 调用析构函数权力对象
- 调用operator delete释放空间
- new[N]
- 调用operator new分配空间
- 调用N次构造函数分别初始化每个对象
- delete[N]
- 调用N次析构函数清理对象
- 调用operator delete释放空间
void Test()
{
int *p1 = new int;//动态分配4字节(1个int)的空间单个数据
int *p2 = new int(3);//动态分配4字节(1个int)的空间并初始化
int *p3 = new int[3];//动态开辟12个字节(3个int)的空间
delete p1;
delete p2;
delete[] p3;
}
注意:malloc和free,new/delete,new[]/delete[]要配对使用,否则可能出现内存泄漏甚至崩溃的问题
问:为什么c++不能使用mallco/free来动态管理内存呢?
答:c语言是面向过程的语言,如果函数出错返回错误码(malloc/free出错返回错误码)
c++面对对象编程,出错会抛异常
3.c++的其他内存管理接口
- void* operator new(size_t size);
- void operator delete(size_t size);
- void *operator new[](size_t size);
- void operator delete[](size_t size);
标准库对函数operator new和operator delete的命名容易让人误解,与其他operator函数(如operator=)不同,他这些函数并不是对new和delete的重载,只是malloc和free的 一层封装,实际上我们不能对new和delete重载
- operator new/operator delete;operator new[]/operator delete[]和malloc/free用法一样
- 他们只负责分配空间,不会调用对象构造函数/析构函数来初始化/清理对象
- 实际上operator new和operator delete只是malloc和free的 一层封装
4.maollc/free与new/delete的区别,联系
- 他们都是动态管理内存的入口
- malloc/free是函数,new/delete是操作符
- malloc要手动计算开辟空间的带下,new开辟空间的大小由系统计算
- malloc/free不调用构造/析构函数,new/delete调用构造函数/析构函数进行初始化和函数清理
- malloc失败返回错误码,new失败抛异常
5.malloc/free,new/delete,new[]/delete[]不匹配使用会出现哪些问题?
1)内置类型
- 不匹配使用也不会出现程序出错、崩溃等问题
- 他们不需要调用构造/析构函数,不用保存count,所以不会出现访问内存出错的问题
2)自定义类型:
- 不调用构造函数,不会崩溃
- 调用构造函数,会崩溃,因为要保存count ,容易出现内存访问出错
- 编译器会自动优化,如果自定义类型中没有定义构造/析构,或者构造/析构函数内部什么都没有做,就不会多开辟用来存储count的4个字节
以下为内置类型的验证(不匹配使用不会出错,但是我们仍建议配对使用):
void Test()
{
int *p1 = new int;
int *p2 = new int(3);
int *p3 = new int[3];
int *p4 = (int*)malloc(sizeof(int));
delete[] p1;
//free(p1);
delete p2;
//free(p2);
//delete p3;
free(p3);
delete p4;
}
自定义类型:
class AA
{
public:
AA(size_t size = 10)
:_size(size)
, _a(0)
{
cout << "AA()" << endl;
}
~AA()
{
cout << "~AA()" << endl;
if (_a)
{
delete[] _a;
_a = 0;
_size = 0;
}
}
private:
int* _a;
size_t _size;
};
情况一:new
void Test()
{
AA* p1 = new AA;
delete p1;
//正确
//delete[] p1;
//错误,因为delete[]对应new[],new[]在开辟空间的时候多加了4,delete[]就会-4向前面四个字节去取,所以会出错
free(p1);
//正确,但是没有调用析构函数
}
情景二: new(3),和情景一情况一样,只是多了初始化
void Test()
{
AA* p2 = new AA(3);
delete p2;
//正确
//delete[] p2;
//程序出错,因为delete[]对应new[],new[]在开辟空间的时候多加了4,delete[]就会-4向前面四个字节去取,所以会出错
//free(p2);
//正确,但是没有调用析构函数
}
情景三:
void Test()
{
AA* p1 = new AA;
AA* p2 = new AA(3);
AA* p3 = new AA[3];
delete p1;
delete p2;
delete[] p3;
//正确
//delete p3;
//错误,delete直接跳过开始4字节从后面开始释放,所以前面的四个字节并没有释放,delete[]从new[]多创建的4个字节处开始释放
//free(p3);
//程序崩溃
}
场景四:
void Test()
{
AA* p4 = (AA*)malloc(sizeof(AA));
free(p4);//正确
delete p4;//析构函数什么都不做时正确
delete[] p4;//错误
}