【C++初阶】动态内存管理

👻内容专栏: C/C++编程
🐨本文概括: C/C++内存分布、C语言动态内存管理、C++动态内存管理、operator new与operator delete函数、new和delete的实现原理、定位new表达式、常见面试问题等。
🐼本文作者: 阿四啊
🐸发布时间:2023.9.18
C/C++中内存分布
C/C++程序在运行时会在计算机的内存中分配不同区域来存储不同类型的数据和指令。一般来说,可以将内存布局分为以下几个主要部分:
内核空间:通常是不可访问的,用于存储操作系统和内核的数据结构。
栈区域:存储非静态的局部变量、函数参数和返回值、函数地址等。
内存映射段:用来映射文件到内存,允许像访问内存一样访问文件。
堆区域:用户可以通过malloc(在C中)或new(在C++中)来手动分配内存,需要手动释放
数据段:存储全局数据、静态变量、常量。
代码段:存储可执行代码和只读常量。

来测验一下C语言学习时期的对内存分布的理解情况:
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);
}
-
①选择:
选项:A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
(1)globalVar在哪里?____ (2)staticGlobalVar在哪里?____
(3)staticVar在哪里?____ (4)localVar在哪里?____
(5)num1 在哪里?____(6)char2在哪里?____ (7)*char2在哪里?____
(8)pChar3在哪里?____ (9)*pChar3在哪里?_____
(10)ptr1在哪里?____ (11) *ptr1在哪里?_____ -
②填空:(1)sizeof(num1) = ____
(2)sizeof(char2) = ____; (3)strlen(char2) = ____;
(4)sizeof(pChar3) = ____;(5) strlen(pChar3) = ____;
(6)sizeof(ptr1) = ____;答案:选择:1~5:CCCAA 6~11:AAADAB 填空:1.40 2.5 3.4 4.4/8 5.4 6.4/8
C语言中动态内存管理方式
动态内存分配函数:malloc、realloc、calloc
malloc:向内存申请一块连续可用的空间,并返回指向这块空间的指针。
calloc:为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
realloc:对动态开辟内存大小的调整。
- realloc在调整内存空间的是存在两种情况:
- 原有空间之后有足够大的空间(原地扩容)
- 原有空间之后没有足够大的空间(异地扩容)

知识点回顾:
void Test ()
{int* p1 = (int*) malloc(sizeof(int));free(p1);// 1.malloc/calloc/realloc的区别是什么?int* p2 = (int*)calloc(4, sizeof (int));int* p3 = (int*)realloc(p2, sizeof(int)*10);// 这里不需要free(p2) 如果释放就会对空间释放两次free(p3 );
}
C++动态内存管理方式
C语言内存管理方式在C++中可以继续沿用,但是在一些情况之下,C++祖师爷考虑到C语言的动态内存管理方式觉得并不妥当,于是提出了C++自己的动态内存管理方式:new 和 delete 操作符。
new/delete操作内置类型
其用法介绍如下:
int main()
{//申请一个int类型大小的空间int* p1 = new int;//申请10个int类型大小的空间int* p2 = new int[10];//申请一个int类型大小的空间,并初始化为1。int* p3 = new int(1);//申请10个int类型大小的空间,初始化前4个元素,其余元素默认初始化为0int* p4 = new int[10]{ 1,2,3,4 };//释放p1/p2/p3地址处的空间delete p1;delete p2;delete p3;//释放p4地址处连续的空间delete[] p4;return 0;
}
⚠️注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],需要匹配起来使用。
new和delete操作自定义类型
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;
}
~A()
{cout << "~A():" << this << endl;
}
private:int _a;
};
int main()
{// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间//还会自动调用构造函数和析构函数A* p1 = (A*)malloc(sizeof(A));A* p2 = new A(1);free(p1);delete p2;A* p5 = (A*)malloc(sizeof(A)*10);A* p6 = new A[10];free(p5);delete[] p6;return 0;
}
⚠️注意:在申请自定义类型的空间时,new会自动调用构造函数,delete会自动调用析构函数,而malloc与free不会。
抛异常
我们在C语言中进行动态内存申请失败时,会返回一个空指针(NULL),那么,在C++中,用new申请内存失败会有一个抛异常操作。
我们可以使用try-catch进行捕获异常。而不需要像C语言那样进行手动检查。
语法格式:
try {// 可能抛出异常的代码块
}
catch (ExceptionType1 e1) {// 处理 ExceptionType1 类型的异常
}
示例:
#include <iostream>
using namespace std;
int main()
{try{char* ch = new char[0x7fffffff];}catch (const exception& e){cout << e.what() << endl;}return 0;
}
简单来说,try所在区域里面的动态内存空间申请失败,会跳至catch所在的代码块中(类似于goto语句),然后报出所对应的错误信息bad_alloc,若内存申请成功,则不会执行catch所在内存块里面的语句,这里简单理解一下就行,后面进阶部分,会细致讲解此语法。
operator new与operator delete函数
operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid *p;while ((p = malloc(size)) == 0)if (_callnewh(size) == 0){// report no memory// 如果申请内存失败了,这里会抛出bad_alloc 类型异常static const std::bad_alloc nomem;_RAISE(nomem);}return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{_CrtMemBlockHeader * pHead;RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));if (pUserData == NULL)return;_mlock(_HEAP_LOCK); /* block other threads */__TRY/* get a pointer to memory block header */pHead = pHdr(pUserData);/* verify block type */_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));_free_dbg( pUserData, pHead->nBlockUse );__FINALLY_munlock(_HEAP_LOCK); /* release other threads */__END_TRY_FINALLY
return;
}
/*free的实现*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
反汇编代码演示
我们还是以往写的stack的代码为例。


我们可以将operator new 和operator delete 显式的写出来,调试转为反汇编代码观察,他们两个只调用了底层的operator new和operator delete 函数。


而对于
new,其实在底层不光调用了operator new函数,也调用了stack自身的构造函数,对于delete,在底层不光调用了operator delete函数,也调用了stack自身的析构函数!
new 和delete的实现原理
内置类型
如果申请的是内置类型的空间,
new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请和释放的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
自定义类型
new的原理- 调用operator new函数申请空间。
- 在申请的空间上执行构造函数,完成对象的构造。
delete的原理- 在空间上执行析构函数,完成对象中资源的清理工作。
- 调用operator delete函数释放对象的空间。
new []的原理- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。
- 在申请的空间上执行N次构造函数
delete[]的原理- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
放空间
free、delete和delete[]
😉思考:free、delete、delete[]能不能混用呢?
场景一:
我们简单手写一个A类,用new去申请一段连续的内存空间,最后分别用free、delete、delete[]三种方式去释放,都能运行成功,所以我们对申请的内存空间进行释放,可以随意用free、delete、delete[]都可以吗?
class A
{
public:A(int a = 1):_a(a){cout << "A()" << endl;}private:int _a;
};int main()
{A* p = new A[10];free(p);//运行成功//delete p;//运行成功//delete[] p;//运行成功return 0;
}
场景二:
下面我们在A类中显式的写了析构函数,并且打印析构函数名,我们去运行这段代码,发现用free和delete均会出现程序崩溃,最后用delete[]去释放才能运行成功,那么是什么原因呢?
class A
{
public:A(int a = 1):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}private:int _a;
};int main()
{A* p = new A[10];//free(p);//崩溃//delete p;//崩溃delete[] p;//运行成功return 0;
}
说明:是因为没有调用析构函数吗?其实不是的,大家可以试一下我们刚写的stack这样的类,不进行释放,也就是没有调用析构函数是不会报错的,顶多出现内存泄露问题。
为了证明以上问题,vs2019的编译器封装性太全了,观察不到其operator delete函数,所以,根据如下的图中解释,这里大家只需要记住此“规则”。

那么场景一该怎么解释呢?其实是因为我们没有写析构函数,此时是编译器做了一些优化和检查,编译器觉得析构函数没有什么事情需要做,于是觉得没有必要指向p2的位置(甚至是没有开辟一个int大小的空间的,这取决于编译器的行为),所以释放的位置也是从p1的位置进行释放,所以利用三种方式释放并没有报出错误。
结论
通过以上场景我们发现,申请内存空间与释放空间不匹配是未定义的行为,所以申请空间是用什么操作符,释放空间就应该对应着匹配使用!!!。
定位new表达式(placement-new)
用operator new开辟一段stack空间,这里的pst指向的只不过和stack是相同大小的空间,还不能算作是一个对象,因为并没有执行构造函数。那么是否可以直接显式地调用构造函数呢?答案是不可以的,因为语法不允许,只能够显式地调用析构函数。
于是,便有了定位new表达式(placement-new)的概念。
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
- 使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表。
下面是一段定位new配合operator new使用的代码,可以理解为等同于new,因为上面我们验证过new在底层是operator new加上对应的构造函数。
而pst->~stack()与operator delete可以理解为等同于delete,
int main()
{stack* pst = (stack*)operator new(sizeof(stack));//等价于pst->stack(); //实际不能显式地调用构造函数new(pst)stack(20);//构造函数有参数,需要进行传参//只能够显式地调用析构函数pst->~stack();operator delete(pst);return 0;
}
- 使用场景:定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显式调构造函数进行初始化。
常见面试题
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地
方是:
- malloc和free是函数,new和delete是操作符。
- malloc申请的空间不会初始化,new可以初始化。
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
什么是内存泄露?内存泄露的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
如何规避内存泄露?
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。 - 采用RAII思想或者智能指针来管理资源。
- 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄
漏检测工具。
相关文章:
【C++初阶】动态内存管理
👻内容专栏: C/C编程 🐨本文概括: C/C内存分布、C语言动态内存管理、C动态内存管理、operator new与operator delete函数、new和delete的实现原理、定位new表达式、常见面试问题等。 🐼本文作者: 阿四啊 …...
Mac电脑安装Zulu Open JDK 8 使用 spring-kafka 消费不到Kafka Partition中的消息
一、现象描述 使用Mac电脑本地启动spring-kakfa消费不到Kafka的消息,监控消费组的消息偏移量发现存在Lag的消息,但是本地客户端就是拉取不到,通过部署到公司k8s容器上消息却能正常消费! 本地启动的服务消费组监控 公司k8s容器服…...
CodeArts Check代码检查服务用户声音反馈集锦(2)
作者:gentle_zhou 原文链接:CodeArts Check代码检查服务用户声音反馈集锦(2)-云社区-华为云 CodeArts Check(原CodeCheck),是自主研发的代码检查服务。建立在华为30年自动化源代码静态检查技术…...
红帽RHCE9.0学什么内容,新版有什么变化
【微|信|公|众|号:厦门微思网络】 一、红帽公司介绍 红帽是首个(也是全球最大、全球领先)的企业开源软件解决方案提供商,在过去 20 几年里,红帽已经成为开源社区里令人尊敬的成员,赞助了数百个开源项目&…...
线性代数的本质(一)——向量空间
文章目录 向量空间向量及其性质基与维数向量的坐标运算 《线性代数的本质》 - 3blue1brown 高中数学A版选修4-2 矩阵与变换 《线性代数及其应用》(第五版) 《高等代数简明教程》- 蓝以中 向量空间 In the beginning Grant created the space. And Grant said, Let there be vec…...
PP-Tracking之C++部署
文章目录 概要环境fastdeploy源码编译PP-Tracking源码编译使用参考概要 PP-Tracking是基于飞桨深度学习框架的业界首个开源实时跟踪系统。针对实际业务的难点痛点,PP-Tracking内置行人车辆跟踪、跨镜头跟踪、多类别跟踪、小目标跟踪及流量计数等能力与产业应用,同时提供可视…...
智慧公厕建设,要以技术为支撑、体验为目的、业务为驱动
#智慧公厕[话题]# #智慧公厕系统[话题]# #智慧公厕厂家[话题]# #智慧公厕驿站[话题]# 在数字化城市与智慧城市的大力推进下,作为社会重要的生活设施,智慧化的公共厕所的建设变得越来越重要。作为城市的基础部件之一,公厕的智慧化建设需要进行…...
通过Sealos 180秒部署一套K8S集群
通过Sealos 180秒部署一套K8S集群 一、主机准备 1.1 主机操作系统说明 序号操作系统及版本备注1CentOS7u9 1.2 主机硬件配置说明 k8s集群CPU及内存最低分别为2颗CPU、2G内存,硬盘建议为100G 需求CPU内存硬盘角色主机名值8C8G1024GBmasterk8s-master01值8C8G1024…...
如何获取美团的热门商品和服务
导语 美团是中国最大的生活服务平台之一,提供了各种各样的商品和服务,如美食、酒店、旅游、电影、娱乐等。如果你想了解美团的热门商品和服务,你可以使用爬虫技术来获取它们。本文将介绍如何使用Python和BeautifulSoup库来编写一个简单的爬虫…...
开启编程之门
自我介绍 目前已经大二了,计算机专业在读,是一个热爱编程,做事踏实专注的人。转眼间一年已经过去了,也接触编程一年了,但开始并没有对所学所想进行很好的总结和输出,这一年也有了新的很多感悟与心得&#x…...
【ES】Too many dynamic script compilations within, max: [75/5m]; 问题处理
问题原因 ElasticSearch5分钟内脚本编译的数量不能超过75个。 解决方法 PUT _cluster/settings {"persistent": {"script.max_compilations_rate": "1000/1m"} }参数可以根据自己需要定义,比如10分钟3000个,3000/10m等…...
LED智能家居灯 开关调光 台灯落地灯控制驱动 降压恒流IC AP5191
产品描述 AP5191是一款PWM工作模式,高效率、外围简单、内置功率MOS管,适用于4.5-150V输入的高精度降压LED恒流驱动芯片。输出最大功率150W,最大电流6A。AP5191可实现线性调光和PWM调光,线性调光脚有效电压范围0.55-2.6V.AP5191 工作频率可以…...
贪心算法的思路和典型例题
一、贪心算法的思想 贪心算法是一种求解问题时,总是做出在当前看来是最好的选择,不从整体最优上加以考虑的算法。 二.用贪心算法的解题策略 其基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保…...
演讲笔记|《一个ppt者的成长故事》
前言:本文为《说服力:工作型PPT该这样做》作者、秋叶PPT团队成员秦阳于2017年1月15日在北京望界无界空间的演讲内容要点总结。 1. 结构化思考(思考能力) 体系:挖多个坑,多个视角(构建体系 – 获…...
【八大经典排序算法】堆排序
【八大经典排序算法】堆排序 一、概述二、思路解读三、代码实现(大堆为例) 一、概述 堆排序是J.W.J. Williams于1964年提出的。他提出了一种利用堆的数据结构进行排序的算法,并将其称为堆排序。堆排序是基于选择排序的一种改进,通…...
Redis五大基本数据类型
1、字符串类型 字符串类型相当于 java 中的 String 类型。Redis 中的 String 类型以二进制方式存储,不会做任何的编码转换,因此不仅仅可以存储文本数据、整数、普通的字符串、JSON、xml文件,还可以存储图片、视频、音频。String 存储的种类虽…...
AI一点通: OpenAI whisper 在线怎么调用,怎么同时输出时间信息?
OpenAI 语音转文字 whisper API提供了两个端点,即转录和翻译,这基于我们最先进的开源大型v2 Whisper模型。它们可以用来: 将音频转录成音频所在的语言。 翻译并将音频转录成英文。 文件上传目前限制为25 MB,支持以下输入文件类型…...
OpenText EnCase Mobile Investigator 查看、分析和报告被调查手机的证据
OpenText EnCase Mobile Investigator 查看、分析和报告被调查手机的证据 全球83.72%的人口拥有智能手机 OpenText™ EnCase™ Mobile Investigator 使调查人员能够轻松分析、审查和报告与其案件相关的移动设备上的证据。 为什么选择OpenText EnCase Mobile Investigator 预算友…...
【JavaScript】video标签配置及相关事件:
文章目录 一、标签配置:二、事件:三、案例: 一、标签配置: 标签名描述src要播放的路径地址autoplay是否自动播放,默认值是false,(Boolean)loop是否循环播放,默认值是false,…...
SpringSecurity 初始化解析
文章目录 前言加载SpringSecurity配置解析配置SpringSecurity 解析器security:http 解析FilterChainProxy的注册过程创建 SpringSecurity 过滤器总结 前言 通过上文分析知道了SpringSecurity对一个请求的具体处理流程。不知道大家是否跟我一样都有几个疑问: Filte…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
pgsql:还原数据库后出现重复序列导致“more than one owned sequence found“报错问题的解决
问题: pgsql数据库通过备份数据库文件进行还原时,如果表中有自增序列,还原后可能会出现重复的序列,此时若向表中插入新行时会出现“more than one owned sequence found”的报错提示。 点击菜单“其它”-》“序列”,…...
