1、C/C++内存分布
①内存分那么多区的原因:不同的数据,有不同的存储需求,各区域满足了不同的需求。
②存放:
临时变量等临时用的变量:栈区;
动态申请的变量:堆区;
全局变量和静态变量等整个程序期间都使用的变量:数据段;
常量、可执行代码等只读数据:代码段;
2、C语言中动态内存管理方式:malloc/calloc/realloc/free
3、C++内存管理方式
C语言的内存管理方式在C++中依然可以使用但是有些情况不方便,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
①new/delete操作内置类型
1°new操作符
动态开辟一个整型:
int* p=new int;
动态开辟一个整型同时赋个初值:
int* p=new int(3);
动态开辟多个整型:
int* p=new int[10];
动态开辟多个整型同时赋值:
int* p=new int[10]{1,2,3};
2°delete操作符
释放申请的一个空间:
delete p;
释放申请的多个空间:
delete[] p;
★new和delete,new[]和delete[]匹配起来使用;
★对于内置类型,malloc和new除了用法上的区别,没有别的区别;
②new/delete操作自定义类型
C语言的动态管理对于自定义类型是不好解决初始化的,所以必须要使用C++提供的new和delete
1°new操作符
动态开辟一个自定义类型:
A* p=new A; //A是一个自定义的类
动态开辟一个自定义类型同时赋初值:
A* p=new A(1); //自定义类型有传一个整型的构造函数
动态开辟多个自定义类型:
A* p=new A[3]; //开辟三个自定义类型的对象
动态开辟多个自定义类型同时赋初值:
A* p=new A[3]{1,2,3}; //隐式类型转换赋初值
A* p=new A[3]{aa1,aa2,aa3}; //利用已有的对象赋初值
A* p=new A[3]{A(1),A(2),A(3)}; //用匿名对象赋初值
★new的本质:开空间+调用构造函数初始化
2°delete
释放申请的一个空间:
delete p;
释放申请的多个空间:
delete[] p;
★delete的本质:调用析构函数+释放空间
③new失败
1°new失败的话会抛出异常,但是异常需要捕获;
2°cout打印char*类型的数据的时候,默认打印的是字符串类型,想要打印地址可以使用printf或者前面加(void*)强制类型转换
4、operator new与operator delete函数
①new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层调用operator delete全局函数来释放空间。(这两个函数不是对new和delete的重载)
②operator new和operator delete封装的malloc和free。malloc失败了返回0,operator new失败了抛异常,这样更符合面向对象的特性。operator delete是为了和operator new配对。
5、new和delete的实现原理
①new和delete是操作符,运行时new直接转换成汇编指令,汇编指令中调用了operator new之后调用构造函数,delete直接转换成汇编指令,汇编指令中调用了析构函数之后,调用operator delete。
即:
new的原理:调用operator new函数申请空间
在申请的空间上执行构造函数,完成对象的构造
delete的原理:在空间上调用析构函数,完成对对象资源的清理工作
调用operator delete函数完成对象空间的释放
new[]的原理:调用1次operator new[]函数申请N个对象的空间
在申请的空间上执行N次构造函数,完成对象的构造
★开空间时会在空间的头部多开四个字节的空间。
★多开四个字节空间是为了告诉delete[]调用析构函数时调用几次
delete[]的原理:在空间上调用N次析构函数,完成对对象资源的清理工作
调用1次operator delete函数完成对象空间的释放
★释放空间时会向前减四个字节开始释放空间
▲如果使用new[]申请空间,使用delete释放空间,逻辑上一定是错的,但是可是报错,可能不报错。报错是因为,new[]申请空间时在空间的前面多申请了四个字节的空间,delete释放是没有从四个字节前开始释放,相当于从中间开始释放的,这是肯定会报错的。不报错的话,就是因为你的类没有显式的写析构函数,并且你的成员变量都是内置类型,new[]申请空间时前面没有多申请前面四个字节的空间,所以使用delete释放也是可以的,但是这属于编译器的优化,是编译器做的,不一样的编译器不一定做法相同。所以写的时候一定要注意匹配的问题。
6、定位new表达式(placemen-new)
①构造函数和析构函数都可以显式调用,析构函数像调用普通函数一样直接调用就好了,而构造函数像普通函数一样直接调用会报错,需要使用定位new来进行显式调用。
②定位new显式调用构造函数的主要场景:
一般定位new都是和内存池一起配合使用的,我们频繁去堆开辟一小块空间是效率非常低的,所以有了内存池的概念,内存池就是一次在堆中申请一块比较大的空间,然后我们创建对象时先去内存池看看空间够不够,空间不够就去堆中开辟,空间够就直接使用内存池内的空间,而内存池的空间是以前开辟的,所以是未初始化的,我们想要初始化就需要使用定位new来显式调用构造函数。
③使用格式:
new(place_address) type 或者new(place_address) type (Initializer_list)
place_address:必须是一个指针
initializer_list:是类型的初始化列表