🍞一.C/C++内存分布
💡内存分布:
👆以上是内存划分示意图
1. 栈区(Stack):
- 用于存储函数调用时的局部变量、函数参数和返回值等数据
- 栈区是自动分配和释放的,随着函数的调用和返回而动态地增长和收缩
- 栈区一般有限定的大小,比如在Linux下一般为8M
2. 堆区(Heap):
- 用于程序运行时的动态内存分配
- 在堆区上使用malloc、calloc、new等函数进行动态内存分配
- 堆区的空间较大,一般可以达到接近2G的大小
3. 静态区(Static):
- 用于存储全局变量和静态变量
- 静态区中的变量在程序的整个执行过程中都存在。
- 静态区的大小是固定的,与程序的运行状态无关。
4. 常量区(Const):
- 存储常量和程序编译出的指令(即可执行的机器指令)
- 常量区中的数据是只读的,不可修改
- 程序执行语句时会根据常量区中的指令依次执行
5. 内存映射段(Memory-mapped Segment):
- 用于高效的I/O映射方式,主要用于装载共享的动态内存库
- 通过内存映射可以将磁盘上的文件映射到内存中,以便程序可以像访问内存一样访问文件中的数据
🍞二.动态内存管理
💡动态内存管理方式:
C语言中的动态内存管理函数有malloc
、calloc
、realloc
和free
。它们的区别可以概括如下:
-
malloc:在堆区上申请指定大小的内存空间
-
calloc:在malloc的基础上,对已开辟的空间初始化为0
-
realloc:针对已有的内存空间进行扩容
-
free:释放通过malloc、calloc或realloc分配的内存空间
对于C++来说,C语言的内存管理方式仍然可以在C++中使用,但由于其麻烦且无法自动处理对象的构造和析构
C++提出了自己的动态内存管理方式即通过new
和delete
操作符进行管。
能自动处理对象的构造和析构
因此,在C++中推荐使用new
和delete
来进行动态内存管理。
1.new和delete操作符
💡new和delete操作符:
对于动态内存分配,使用new
操作符的方法如下:
- 使用
new
关键字,后跟数据类型和可选的数据个数(默认为1)。
int* ptr = new int; // 动态申请一个int大小的空间
int* arr = new int[5]; // 动态申请一个包含5个int元素的数组
注:当数据个数为1时,可以省略数据个数。
- 分配的内存大小为:sizeof(数据类型) * 数据个数。编译器会在堆区动态申请一块连续的空间。
对于释放动态申请的内存,使用delete
操作符的方法如下:
- 使用
delete
关键字,后跟指向动态内存的指针和可选的中括号(适用于释放数组内存)。
delete ptr; // 释放动态申请的单个内存空间
delete[] arr; // 释放动态申请的数组内存空间
注:使用delete[]
释放动态数组内存是必要的,以确保正确释放数组元素的内存
对于非数组类型,使用delete
来释放内存。
❗特别注意:
我们可以在申请空间的时候,同时对空间进行初始化
//动态申请大小为sizeof(int)的空间,并初始化为0
int* p1 = new int();
//动态申请大小为sizeof(int)的空间,并初始化为1
int* p2 = new int(1);
//动态申请大小为sizeof(int)*10的空间,并初始化为0
int* p3 = new int[10](0);
-
但注意,并
不存在new int[10](1)
的存在,因为标准不支持 -
但是可以列表初始化
int *p=new int [10]{1,2,3,4,5,6,7,8,9,10};
- 对于动态内存的释放,确保
delete
操作符与new
操作符相互匹配
- 当使用
new
操作符动态申请了单个元素的空间时,应该使用delete
操作符来释放内存 - 当使用
new
操作符动态申请了连续的空间(数组)时,应使用delete[]
操作符来释放内存
- 如果类型不匹配,即使用
delete
释放了使用new[]
申请的数组内存,或使用delete[]
释放了使用new
申请的单个元素内存,都会导致未定义的行为,可能引发内存泄漏或其他错误。
但是下面这样子的话,不会出现内存泄漏,但是不建议使用
- 因为基本类型没有析构函数,因此回收基本类型的数组空间用delete和delete[]都可以
- 总的来说,为了代码的规范化和安全考虑,
new
和delete
需要匹配使用
2. 针对不同类型的处理
❗new和delete会针对不用类型做不同的处理:
-
- 对于内置类型:
new
和malloc
的使用几乎没有区别,它们都用于分配内存空间。但是使用new
可以进行对象的初始化,默认会调用内置类型的构造函数。
- 对于内置类型:
-
- 对于自定义类型:
new
在分配内存的基础上,会自动调用自定义类型的构造函数对对象进行初始化。而使用malloc
只会根据自定义类型的大小分配对应大小的空间,不会进行对象的构造和初始化。
- 对于自定义类型:
-
- 在释放内存时,
delete
会首先调用自定义类型的析构函数对对象进行析构,然后再释放内存空间。而free
只会释放内存空间,不会调用自定义类型的析构函数。
- 在释放内存时,
-
- 由于
new
操作符在分配内存的同时会调用构造函数,因此在使用delete
释放动态内存时,确保类型相互匹配非常重要。这样可以保证对象正确地被析构并释放资源。
- 由于
❗初始化的原因
- 之所以能进行初始化,本质其实是:利用了对象自身的构造函数进行初始化
-
当使用
new
关键字创建对象时,如果不带括号,则对象将使用默认构造函数进行初始化。如果默认构造函数是由编译器自动生成的,则不会对数据进行任何处理。 -
而当使用带括号的
new
语法时,表示对象将使用参数构造函数进行初始化。如果括号内没有传递参数,则默认将其初始化为0。
int* pa1 = new int;
int* pa2 = new int();
int* pa3 = new int(1);
❗针对自定义类型的处理图示
- new
Stack* pst = new Stack;
Stack* pt = (Stack*)malloc(sizeof(Stack));
可以明显的看到pst是通过new出来的,pst调用了默认构造函数,*pst的值被初始化了
ps是通过malloc出来的,那么就没有调用默认构造函数
- 在动态申请内存时:
-
malloc针对
自定义类型
仅仅是开辟空间,但无初始化 -
而new则会根据其类型是
自定义类型
,在动态申请出空间的时候,自动调用其默认构造函数进行初始化
- delete
- 在释放申请的内存时:
free无论是内置类型
还是自定义类型
,都只会将动态申请的空间进行释放
而delete则会根据其类型是自定义类型
,在释放动态申请的空间前,先调用自定义类型的析构函数对空间中创建的对象进行资源的释放,最后才对动态申请的空间进行释放
🍞三.new和delete的底层原理
💡new和delete的底层原理:
-
new 和 delete 是用户进行动态内存申请和释放的操作符
-
operator new 和 operator delete 是系统提供的 全局函数
-
本质:new 会在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间
-
综上:operator new实质也是通过malloc来申请空间的
-
即调用new的时候其实会转换为调用
operator new ➕ 构造函数
,从new申请空间失败会抛异常也可以侧面说明new底层调用的是operator new而不是malloc
👉四.定位new
定位new
表达式用于在已分配的原始内存空间中调用构造函数初始化对象。它适用于以下情况:
-
内存池:当使用内存池分配的内存块没有被初始化时,可以使用定位
new
来调用构造函数进行初始化 -
定制的内存分配:当需要在特定的内存位置上创建对象时,可以使用定位
new
来在指定的内存地址上调用构造函数进行初始化
定位new
的工作原理是通过在已分配的内存地址上调用对象的构造函数来初始化对象。这样可以绕过正常的对象实例化过程,直接对已分配的内存空间进行初始化
简而言之,定位new
就是在已经分配好的内存空间中调用构造函数来初始化对象。这种技术常用于需要手动管理对象生命周期的情况下,例如内存池或者定制的内存分配
1️⃣给对象分配了空间,但未进行初始化
2️⃣使用定位new在类外调用其构造函数进行初始化
对于delete操作符来说
以下操作便是delete操作符
//析构函数可以显示调用
pt->~Date();
operator delete(pt);
🍞五.内存泄漏的危害
- 内存泄漏是指程序在运行过程中分配的内存没有被正确释放,导致这些内存无法再被重复利用,造成内存资源的浪费
-
内存消耗:内存泄漏会导致程序不断分配新的内存而无法释放,最终导致可用内存逐渐减少,耗尽系统的内存资源。
-
程序性能下降:内存泄漏会增加程序内存的使用量,导致频繁的内存申请和释放操作,影响程序的性能,使其变得缓慢和低效。
-
系统崩溃或异常:当内存泄漏严重时,会导致系统内存资源耗尽,无法处理更多的任务和进程,最终导致系统崩溃或异常。
-
数据丢失或损坏:未被释放的内存中可能存储着程序所需要的数据,如果无法访问到这些数据,就可能导致数据丢失或损坏,影响程序的正确执行。
❗注意
- 内存泄漏是指指向内存的指针丢失了,而不是指空间丢了,这样子就找不到地址,没办法释放资源
🍞六. 总结
- C++的内存管理常使用new和delete进行
- new相当于operator new和构造函数
- delete相当于operator delete+析构函数