放在专栏【C++知识总结】,会持续更新,期待支持🌹
本章知识大致总结
我们的计算机,为了更好的对内存空间进行管理,将内存空间划分为以下几个区域:栈区、内存映射段、堆区、数据段、代码段,以及内核空间。C与C++在内存空间的分布是一致的。
栈区
内存映射段
堆
数据段
代码段
举个具体的例子,如下:
在 C语言阶段,我们是使用 malloc/calloc/realloc用来进行动态内存管理的,搭配 free进行释放,关于这三者之间的使用方法以及注意事项,在之前的一篇文章中,有更加详细的介绍,这里就简单提一嘴。更为详细的介绍还请参考之前的文章, 点击跳转。undefined
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
// 动态申请一个int类型的空间,不做初始化
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间,并将前四个元素初始化为1 2 3 4 ,其余初始化为0
int* ptr3 = new int[10]{1,2,3,4};
我们发现,new的使用极其简单,没有类型强转,也没有空指针的检查,并且new还有一个与C语言malloc最大的区别,就是new一个自定义类型对象时,会自动调用该自定义类型的构造函数。
总结:
delete后面直接跟要释放的空间地址,这里需要注意的是,在delete一个数组空间时,要用delete[],与new 中的[]配套使用。如下:
// 动态申请一个int类型的空间,不做初始化
int* ptr1 = new int;
delete ptr1;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
delete ptr2;
// 动态申请10个int类型的空间,并将前四个元素初始化为1 2 3 4 ,其余初始化为0
int* ptr3 = new int[10]{1,2,3,4};
delete[] ptr3;
//注意:假如new中有[],delete也要加[]
当然,与free的最大不同之处就在于delete一个自定义类型时,会先调用该自定义类型的析构函数。
总结
我们看到operator,可能第一反应就是运算符重载,实际上这两个是个例外。operator new 和operator delete是系统提供的全局函数。我们可以观察它们的反汇编,来查看它们的底层实现。
因此,我们可以这么来说,operator new与operator delete可以看作是malloc与free的封装。
具体使用方法与malloc/free一致的。
//malloc
int* arr1=(int*)malloc(sizeof(int));
//operator new
int* arr2=(int*)operator new(sizeof(int));
//free
free(arr1);
//operator delete
operator delete(arr2);
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
new的原理
delete的原理
new TN的原理
delete[] 的实现原理
结论:一定要注意匹配使用,不要混着用,不然释放的最终结果是不确定的,对于普通的内置类型,编译器不会报错,但是对于一些特殊情况,则会出现问题,这里简单谈一谈。
假如类成员变量中有成员向内存申请开辟空间,此时假如用free,就会造成内存泄漏。如下:
我们在给自定义类型开辟一个数组空间时,new与delete都用到了[],这里谈一谈[]的作用,实际上,在使用new开辟空间的时候,会在最前端先开辟一个整型空间,用来标记[]内的数字。但是返回给我们的是[]后面的空间的起始位置。如下:
总结起来就一句话:配套使用即相安无事!
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。简单来说,就是对已经存在的空间进行初始化。(目前了解即可)
class A
{
public:
A(int a=10)
:_a(a)
{}
~A()
{
_a = 0;
}
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* ps = (A*)malloc(sizeof(A));
if (ps == NULL)
{
perror("malloc fail");
}
ps->Print(); // ps现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
//有执行 ,此时_a的值为随机值
//此时我想对这块已有空间进行初始化,用定位new,调用A的构造函数对ps指向空间初始化
new(ps)A;
ps->Print(); //此时_a==10
//也可以进行传参,再次进行初始化
new(ps)A(100);
ps->Print(); //此时 _a==100
return 0;
}
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。 所谓内存池就是从堆上申请出一块内存,放在内存池里,这样后面再申请空间时就不需要从堆上申请,而是直接拿内存池里的空间,会一定程度上提高效率。但是由于内存池里的空间都是未进行初始化的,而定位new就是对已经存在的空间进行初始化,作用在此。
end
生活原本沉闷,但跑起来就会有风!🌹