1. C/C++内存分布
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共 享内存,做进程间通信。
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段--存储全局数据和静态数据。
5. 代码段--可执行的代码/只读常量。
提问:图中*char2 *pChar3 在哪里?
*char2位于栈中,*pChar3位于代码段。
2. C语言中动态内存管理方式:malloc/calloc/realloc/free
1.malloc
malloc函数,void *malloc(unsigned int num_bytes);
里面参数代表需要申请多少bit空间。
2.calloc
calloc函数,void *calloc(size_t n, size_t size);
1.第一个参数代表申请类型的个数,第二个参数代表一个类型的大小。
(申请20个int类型空间,会int *p = (int *)calloc(20, sizeof(int)))
2.calloc却在申请后,对空间逐一进行初始化,并设置值为0;
3.realloc
realloc函数,void *realloc(void *mem_address, unsigned int newsize);
1.第一个参数是原空间的起始位置,第二个参数要修改到的大小。
(int*a=(int*)malloc(sizeof(int)); int* p=(int*)realloc(a,sizeof(int)*2 );)把a指向的1int的空间扩容到2 int.
2.如果a指针指向的空间后面还有空间足够扩容,会在a后面进行扩容。
3.如果没有a空间后面足够的空间扩容,p会再找一块sizeof(int)*2的空间,把a的内容拷贝过来,并free a;
4.如果找不到就返回null;
如果p3没有找到新空间,那么p2仍存在,需要free;
3.C++内存管理方式
1.new/delete操作内置类型
C++通过new和delete操作符进行动态内存管理。
int main()
{
int* a = new int;//申请一块int字节的空间
int* b = new int(1);//申请一块int字节的空间并初始化1
int* c = new int[10];//申请一块int*10大小的空间
int* d = new int[10] {1, 2, 3, 4}; //申请一块int*10大小的空间,并初始化(没给值的初始化0)
delete a;//销毁空间
delete b;
delete c;
delete d;
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[],注意:匹配起来使用。
2.new和delete操作自定义类型
1.new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构 造函数和析构函数
对于自定义类型,malloc申请了空间但不能直接对类的成员变量初始化。
而new可以自动调用默认构造,没有默认构造也可以显式传参进行构造。
对于多参数的构造函数,我们需要把要传的参数用{}括起来。
4. operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的 全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new比malloc多了什么?
1.operator new 实际也是通过malloc来申请空间,但malloc申请空间失败的话是返回null,打印错误编号。而operator new申请失败会进行抛异常,打印出更完善的错误信息。
2.operator new 还会调用类的构造函数。
注:在一个栈类,有int*_a,int _top,int _capacity 三个成员变量。而_a 又指向了一块空间。
operator new申请的是3个成员变量的空间,调用的构造函数申请的是_a指向的空间。
operator delete销毁的是3个成员变量的空间,调用的析构函数销毁的是_a指向的空间。
5. new和delete的实现原理
1.内置类型
对于内置类型,new和malloc delete和free没有本质区别。
new delete是申请/消除单个空间。new[ ] delete[ ]是操作多个对象。
new申请失败会抛异常,而malloc失败会返回null。
2 自定义类型
new原理
1.调用operator new函数申请空间。
2.调用构造函数,完成初始化。
delete原理
1.调用析构函数,完成对象中资源清理的工作。
2.调用operator detele函数销毁空间。
new[ n ] 原理
1.调用operator new[]函数,底层仍是调用operator new函数,完成n个对象的申请空间。
2.在申请空间上调用n次构造函数。
detele[ ]原理
1.在申请空间上调用n次析构函数,完成n个对象的中资源清理的工作。
2.调用operator detele[]函数,底层仍是调用operator detele函数释放空间。
6. 定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表
int main()
{
A* p1 = (A*)operator new(sizeof(A));//申请空间
//p1->A(); 构造函数不能直接调用
new(p1)A; //显式实例化调用 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();//析构函数可以直接调用
operator delete(p1);//销毁对象
//对于多个对象
//1.先申请一堆空间
A* p2 = (A*)operator new(sizeof(A) * 10);//不加[]也行
//2.用for循环一个对象一个对象初始化
for (int j = 0; j < 10; j++)
{
new(p2+j)A(j);
}
//new(p2)A[10]{ 1,2,3 };//一般个数少的可以
//3.用for循环一个对象一个对象完成资源回收
for (int i = 0; i < 10; i++)
{
(p2 + i)->~A();
}
//4.最后销毁
operator delete(p2);//不加[]也行,底层也是调用的operator delete
}
new和delete一定要配对使用吗?
如果成员变量没有资源没有清理,这样看起来好像没什么问题。
为什么对多个自定义对象free会出错呢?
我们可以看出new开辟了48个字节
我们明明开辟10*int 40个字节,为什么编译器会开48个字节呢?
对一块空间需要调用多次析构函数,那么编译器会开8字节(vs2022)+需要开辟的空间。
多开辟的8个空间是用来记录对一块空间需要调多少次析构函数。
从图中可以看出p1前有8个字节,且值为0a(10)。
如果我们用delete p1;来销毁是否可以呢?
可以看出还是出错了。
这是因为只有delete[] p1;才会连同8个计数的字节一起销毁。
从下面的图中就可以看出,delete[]会从p1位置减去8
正是因为编译器多开了8个字节来记录有多少个对象(为了确认调用析构的次数),
delete[ _ ] 才可以不用我们专门传个数。
new A[10];(开辟多个对象)编译器一定会多开字节吗?
这里编译器并没有多开字节记录需要调用析构的次数,因为类中并没有析构函数。
所以这里编译器只开了40字节大小的空间。
结论:建议配对使用。new A[N]和delete[]A new A和delete A malloc和free
7.malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
1. malloc和free是函数,new和delete是操作符
2. malloc申请的空间不会初始化,new可以初始化
3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可
4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理