在c语言中内存管理函数为malloc和free,而在c++中内存管理的函数则是new和delete。
首先来看new和delete对于申请的内置类型的空间是如何处理的
内置类型的处理
申请连续的多个空间
void test1()
{
int* ret = (int*)malloc(sizeof(int) * 10);
int* rett = new int[10];//和malloc 一样都是在堆上申请10个整型的空间
free(ret);
delete[] rett;//这里是多个空间的开辟所以delete需要加[]
}
可以看到使用new明显简便很多,不需要强转。
申请单个的空间
void test2()
{
int* ret = (int*)malloc(sizeof(int));
int* rett = new int;//这里就只申请了一个整型大小的空间
delete rett;//对于单个空间的释放不需要加[]
free(ret);
}
依旧是比使用malloc简便很多
申请时即初始化
void test3()
{
int* ret = (int*)calloc(1, sizeof(int));//创建单个整型的空间,并将内部的值初始化为0
int* rett = new int(2);//创建单个整型的空间并将里面的值初始化为2
free(ret);
delete rett;//单个数据所以不需要[]
}
需要注意的是c++中new不能[]和()一起使用。还有便是申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],一定要匹配起来使用。
从上面就可以看出在内置类型的处理上,new只是简化了malloc的使用。
那么对于自定义类型呢?
这里以一个栈为例
自定义类型的处理
class stack
{
public:
stack(int x = 4)
:_capacity(4)
,_size(0)
{
_a = new int[x];//开辟空间
}
private:
int* _a;
int _capacity;
int _size;
};//这只是一个简单的栈
int main()
{
stack* st = (stack*)malloc(sizeof(stack));
stack* sst = new stack;
free(st);
delete sst;
return 0;
}
从监视上就可以看到,st并没有调用构造函数,而sst在创建空间之后还自动的调用了构造函数。而这也是为何要使用new而不是使用malloc,malloc只是将栈的空间创建好了但是对于内部的处理一个也不会做,而new则会在创建空间之后自动的调用构造函数。并且delete函数也会调用析构函数,而free则不会。
在讲解new和delete函数的实现原理之前需要先了解一个c++中封装的malloc 和free函数也就是operator new 和operator delete函数
operator new和operator delete 函数
那么这两个函数的功能是什么呢?
下面是功能演示
class stack
{
public:
stack(int x = 4)
:_capacity(4)
,_size(0)
{
_a = new int[x];//开辟空间
}
~stack()
{
_capacity = 0;
_size = 0;
delete[] _a;
}
private:
int* _a;
int _capacity;
int _size;
};//这只是一个简单的栈
int main()
{
stack* sst = (stack*)operator new(sizeof(stack));
operator delete(sst);
return 0;
}
从监视就可以看到这个函数和malloc函数可以说是一样的,不会调用构造函数,只会创建空间。
而new和delete的功能实现就离不开这两个函数
new和delete的实现原理
new的原理
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
new T[N]的原理
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对 象空间的申请
- 在申请的空间上执行N次构造函数 delete[]的原理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释 放空间
需要注意的是operator delete和operator new并不是对new和delete的操作符重载,而是库中的函数名称就叫这个。那么如何验证呢?可以通过观察反汇编来验证。
可以看到都调用了operatoe new和operator delete函数以及默认构造和析构函数。
前面说过new和delete的使用必须匹配那么如果不匹配会出现什么问题呢?
new和delete不匹配使用
首先如果是对于上面的栈类如果你使用new开辟空间,然后使用free释放空间,那么不用想百分百会出现问题,因为栈在构造函数是重新开辟了空间的,而free不会调用析构那么在构造函数那里开辟的空间就不会被释放造成内存泄漏。用图像表示:
那如果是下面这样呢?
class A
{
public:
A(int x = 10)
:_a(x)
{
}
private:
int _a;
};//现在并没有显示写析构函数
void test5()
{
A* a2 = new A[10];
free(a2);
}
int main()
{
test5();
return 0;
}
如果你是这么写的那么就不会出现问题。但如果你是用下面的这种写法就会崩溃。
class A
{
public:
A(int x = 10)
:_a(x)
{
}
~A()
{
cout << "~A()" << endl;
}//显示写了析构函数
private:
int _a;
};//现在并没有显示写析构函数
void test5()
{
A* a2 = new A[10];
free(a2);//这里即使你使用delete A也会报错
//delete[] A //必须这么写才会正确不报错
}
int main()
{
test5();
return 0;
}
那么为何会出现这种情况呢?
这就要涉及到new开辟空间的方式了如下图
首先当你使用new区开辟多个空间并且在这个自定义类型中含有析构函数的时候,在申请空间的时候会多调用四个字节的大小用来存10,而返还给p2的地址则如图。这个10便是用于delete函数要调用多少次析构函数的。如果你不正确的去使用delete[],那么最后调用operator delete函数的时候,会将存放10的那个空间略过,由此导致错误。如果你使用delete[]那么这个指针会先向前然后调用10次析构,最后从正确位置释放空间。
定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式: new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如 果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
int main()
{
A* ptr = (A*)operator new(sizeof(A));//如果这么写的话只是将类A的空间开辟出来了,并没有构造
//如果是栈,那么如果不进行构造是不可能使用的,但是构造函数是不能显示调用的
//析构函数可以显示调用
//ptr->A();//这样是不允许的
//ptr->~A();//析构函数可以显示调用
//那若想显示调用构造就要使用定位new
new(ptr)A(58);//括号里面填初始化参数
ptr->~A();
return 0;
}
当然定位new主要是用于内存池的,但是我暂时还没有学习到那里,所以只是先掌握定位new的使用。
异常处理
在c中,如果malloc开辟空间失败必须使用if去判断然后手动去返回-1,而c++除了这一种方法外还增加了一种方法也就是抛异常。使用如下
int main()
{
try {
char* ptr = new char[0x7fffffff];//创建一个不可能创建出的空间在32位下
}
catch (const exception& e)//捕捉异常
{
cout << e.what() << endl;
}
}
需要注意的是即使你将创建空间的函数封装到func函数里面,然后再func函数里面创建空间之后还做了一些操作,只要创建空间失败,会直接从函数中跳出和goto一样直接去执行catch。如果开辟空间成功则不会进行catch操作。当然你创建空间的函数必须放到try中否则也不会处理异常
当然这里我也只是暂时掌握基本用法而已。
希望这篇博客能对你有所帮助,如果发现了错误,欢迎指出。