C++——内存管理——new和delete的超详细解析

文章目录

回顾C语言中的内存管理

内存四大区

|------------------------------------------------------------------------------------------------------------|
| 1 栈区:主要存放局部变量,函数参数等。 2堆区:malloc,calloc ,realloc开辟的空间。 3 常量区:字符串常量,const常量,只读不改。 4 静态区:用于存储全局变量,static 静态变量 |

一道经典题目

如下

cpp 复制代码
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}
1. 选择题:
   选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
   globalVar在哪里?____   
   staticGlobalVar在哪里?____
   staticVar在哪里?____   
   localVar在哪里?____
   num1 在哪里?____
   
   char2在哪里?____   
   *char2在哪里?___
   pChar3在哪里?____      
   *pChar3在哪里?____
   ptr1在哪里?____        
   *ptr1在哪里?____

答案如下图

malloc ,calloc和realloc的区别

1malloc

|----------------------------------------------------|
| malloc 的声明为:void* malloc(size_t) 分配指定大小的内存块,不初始化。 |

2calloc

|-------------------------------------------------------------------------------------------------------|
| calloc 的声明为:void* calloc(size_t num,size_t size ),参数为变量的数量和每个变量的大小。 相较于malloc()不会初始化,calloc()会初始化成0。 |

3realloc

|---------------------------------------------------------------------------------------------------------------------------------------|
| realloc的声明为:void* realooc(void* ptr,size_t new_size)重新调整之前分配的内存块大小,(可能会移动内存),会保留原有的数据,对新增的数据不初始化。也可以用于缩小内存。当给ptr传nullptr时等同于malloc。 |

C++的内存管理(new delete)

new

作用

在堆区动态开辟内存,自动初始化,返回对应类型指针。

语法

对于内置类型

cpp 复制代码
// 单个变量
int* p = new int;
int* p2 = new int(10); // 初始化值10

// 数组
int* arr = new int[5];

// 释放
delete p;
delete[] arr;

对于自定义类型

cpp 复制代码
int main()
{
	Date* d1 = new Date;
	Date* d2 = new Date[3];//调用默认构造初始化
	Date* d3 = new Date[3]{ Date(2026,5,20),Date(2026,5,21),Date(2025,6,9) };//用匿名对象拷贝构造初始化
	Date* d4 = new Date[3]{ {2026,5,20},{2026,5,21}, {2025,6,9} };//用隐式类型转换初始化
	delete d1;
	delete[] d2;
	delete[]d3;
	delete[] d4;
	return 0;
}

new的优势

|----------------------------------------------------------------------------------------------|
| new 的优势主要在于可以调用构造函数初始化,这样就使我们定义自定义类型时方便了很多。比如在链表中我要创建一个节点并初始化,只需要一个new就搞定了。而不是先malloc再手动初始化。 |

判断new是否成功

|--------------------------------------------|
| 需要捕获异常来判断,现在我们先不用了解这个,后面再说。其实一般也不会失败,不用担心。 |

new和delete的底层原理

1. new(加强版malloc)

new 操作符在底层实际上分为两个步骤:

  1. 调用 operator new 函数申请内存空间:operator new 的底层实现就是 malloc,负责在堆上分配指定大小的内存。
  2. 在申请的空间上调用构造函数:对于自定义类型,new 会自动调用其构造函数完成对象的初始化;对于内置类型,如果提供了初始值,也会进行相应的初始化。

2. delete

delete 操作符的执行过程也分为两步:

  1. 调用析构函数:在要释放的对象空间上执行析构函数,完成对象内部资源的清理工作(如关闭文件、释放子对象等)。
  2. 调用 operator delete 释放内存:operator delete 的底层实现就是 free(),负责将内存归还给系统。

3. new T[N]

new T[N] 用于动态分配对象数组,其底层原理为:

  • 调用 operator new 申请连续内存:一次性申请能够容纳 N 个对象的内存空间。
  • 调用 N 次构造函数:对数组中的每一个元素依次调用构造函数进行初始化(对于内置类型,若未指定初值,则保持未初始化状态)。

4. delete[]

delete[] 用于释放对象数组,其执行流程为:

  • 执行 N 次析构函数:按照数组元素的逆序(或实现定义的顺序)依次调用每个对象的析构函数,确保每个对象的资源都被正确清理。
  • 调用 operator delete 释放整块内存:最后通过 operator delete 释放之前申请的连续内存空间。

5. new 与 delete 必须配对使用

重要原则:new 和 delete、new T[N] 和 delete[] 必须严格配对使用,否则会导致一些错误,常见错误包括:

  • new 配 free():不会调用析构函数,可能导致资源泄漏(如文件句柄未关闭、子对象内存未释放)。
  • new T[N] 配 delete:由于内存布局信息不一致,通常会导致程序崩溃(尤其是自定义类型带有析构函数时)。
错误示例及后果

示例1:new 配 free()

cpp 复制代码
Date* d1 = new Date;
free(d1);  // 错误:未调用析构函数
  • 后果:程序不会立即崩溃,但析构函数未被调用,可能导致内存泄漏或资源泄漏。在 VS 中会产生警告。

示例2:new T[N] 配 delete

cpp 复制代码
Date* d1 = new Date[10];
delete d1;  // 错误:应使用 delete[]
  • 后果 :程序通常直接崩溃。原因是编译器在分配数组时可能在对象内存前存储了数组大小等信息,而 delete 无法正确识别该布局,导致开辟内存的位置和释放内存的位置不一致。

    原理如下图:delete直接在中间释放内存

malloc /free和new/delete的区别

共同点

|--------------------|
| 都需要往堆上申请空间,都需要手动释放 |

不同点

1 使用方面:

|-----------------------------------------|
| 1 malloc()和free()是函数,new和delete是操作符。。。。 |

|---------------------------------|
| 2 malloc申请的空间不会初始化,new可以初始化。。。。 |

|-----------------------------------------------------------------------|
| 3 malloc申请空间的大小需要手动传递,而new后面直接跟类型即可,如果要开辟多个对象,后面加上[ ] 指定对象个数即可。 。。。 |

|-----------------------------------------|
| 4返回类型是void*需要强转,而new不需要,因为new后面跟的就是类型。 |

2 底层功能不同:

|-----------------------------------------|
| malloc申请空间失败会返回NULL,因此需要判空,而new则需要捕获异常。 |

|-----------------------------------------------------------------------------------------------------|
| ###较重要的一点是:申请自定义类型的时候,malloc和free只会开辟空间,不会调用构造和析构。这就非常挫了。而new会在开辟空间后调用构造函数初始化,delete会在释放空间之前调用析构函数。 |

new和delete的底层功能示意

以栈Stack为例

定位new(placement_new)

作用

|----------------------------------------------------------------------------------------------|
| 主要就是用来显示调用构造函数的。构造函数不是能自动调用吗?这其实是用于内存池中的对象的,因为从内存池中开辟的空间不会初始化。。。。而析构函数是可以直接调用的,但构造就需要借助定位new |

格式

使用格式是: new (place_adrress ) type (initializer_list) ,其中place_adrress是指针的意思,initializer_list是参数的意思。

一个使用场景(这里的operator new和malloc()是差不多的)。

内存池

内存池应该是后面才会学习的东西,我们现在先来简单了解一下。

简单来说内存池就是堆给的一块封地 ,在这块封地上,归某个诸侯(某个需要分配内存的进程)所统治 ,别的地方的人人无权干涉。

那这样有什么作用呢?是为了提高内存申请的效率(有些时候我们需要高频申请释放内存)。当有多个进程同时向堆申请内存时,难免需要排队啥的,效率会比较低下,把堆也搞得比较烦。而堆就说:这样吧,你跟我说你大概需要多大的内存,我直接一次性分给你。

大概的逻辑就是这样,具体的细节还需要后面的学习。

相关推荐
Shadow(⊙o⊙)8 小时前
Shell进程替换,自定义Shell解释器——字符串库函数灵活操作!
linux·运维·服务器·开发语言·c++·学习
_F_y8 小时前
树形 DP 从入门到进阶:普通树形DP、树形背包、换根DP
c++·动态规划
数智工坊8 小时前
PyCharm 运行 Python 脚本总自动进 Test 模式?附 RT-DETRv2 依赖缺失终极排坑
开发语言·ide·人工智能·python·pycharm
再写一行代码就下班8 小时前
根据给定word模板,动态填充指定内容,并输出为新的word文档。(${aa}占位符方式且支持循环动态表格)
java·开发语言
七夜zippoe8 小时前
DolphinDB流数据表:创建与订阅
开发语言·订阅··dolphindb·数据表
Hua-Jay9 小时前
OpenCV联合C++/Qt 学习笔记(二十三)----图像校正及单目位姿估计
c++·笔记·qt·opencv·学习·计算机视觉
彦为君9 小时前
JavaSE-05-字符串(全面深入)
java·开发语言·python·ai·ai编程
charlie1145141919 小时前
现代C++特性指南(4)——完美转发与移动语义实战
开发语言·c++·现代c++
kels88999 小时前
实时外汇api的节假日交易时间表,能自动判断休市吗?
开发语言·经验分享·笔记·python·金融·区块链