C++好难(5):内存管理
这一节学完,我们 C嘎嘎 就算是正式入门了,但是之后的课还会更上一阶d(ŐдŐ๑) 继续坚持!
【本节目标】
1. C/C++内存分布
2. C语言中动态内存管理方式
3. C++中动态内存管理
4. operator new与operator delete函数
5. new和delete的实现原理
6.常见问题
目录
【本节目标】
1. C/C++的内存分布
2.C语言中的动态内存管理方式:malloc/calloc/realloc/free
1)malloc
2)calloc
3)realloc
4)free
3.C++内存管理方式
3.1new和delete的基本操作
3.2new和delete操作符自定义类型
3.3总结:
4.operator new与operator delete函数
5.new和delete的实现原理
5.1内置类型
5.2自定义类型
6.常见问题:
1)malloc/free和new/delete的区别
2)内存泄漏
3)内存泄漏分类
4)如何避免内存泄漏
我们都知道在 C语言 中,可以使用 malloc、realloc、calloc 来开辟空间,使用 free 来销毁空间
那我们 C++ 应该怎样去申请空间呢?
1. 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";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.选择题
globalVar 存储于 数据段
staticGlobalVar 存储于 数据段
staticVar 存储于 数据段
localVar 存储于 栈
localVar1 存储于 栈
num1 存储于 栈
char2 存储于 栈因为 char2 是一个数组,存在栈上面的,而 “abcd” 是存在常量区的常量,只是拷贝给了 char2
*char2 存储于 栈数组名就是首元素的地址,也就是 char 是地址,*char2 相当于对 cahr2 进行解引用,找到它的内容,那么它的内容是存储在栈上面的
pChar3 存储于 栈pChar3 是一个指针变量,这个指针变量是在栈上面开的
*pChar3 存储于 代码段pChar3 是一个指针变量,它存的是一个地址,它指向常量区的字符串 “a b c d”,*pChar 就是对这个指针变量解引用,找到了它的内容,也就是 “abcd” ,所以它是存在代码段的
ptr1 存储于 栈
*ptr1 存储于 堆
2.填空题
注意:sizeof 是求字节大小,strlen 是求字符串长度的。
sizeof(num1) = 40(算对象占用空间的大小)
sizeof(char2) = 5(char2 是一个字符数组,求大小要计算 ‘\0’)
sizeof(pChar3) = 4/8 (指针在 32 位平台大小是 4,64 位平台大小是 8)
sizeof(ptr1) = 4/8
strlen(char2) = 4(char2 是一个字符数组,求长度不计算 \0)
strlen(pChar3) = 4
其内存分布图如下:

说明:
- 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
- 3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 4. 数据段--存储全局数据和静态数据。
- 5. 代码段--可执行的代码/只读常量。
2.C语言中的动态内存管理方式:malloc/calloc/realloc/free
1)malloc
malloc 函数的功能是开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个 NULL(空指针)。
它使用的时候,传参只需传入需要开辟的字节个数。
2)calloc
calloc 函数的功能也是开辟指定大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个 NULL(空指针)。

calloc 函数传参时需要传入开辟的内存用于存放的元素个数和每个元素的大小。
calloc 函数开辟好内存后会将空间内容中的每一个字节都初始化为 0。
3)realloc
realloc 函数可以调整已经开辟好的动态内存的大小,
第一个参数是需要调整大小的动态内存的首地址,第二个参数是动态内存调整后的新大小。
如果第一次使用realloc时检测到首地址未开空间,则realloc和malloc的功能一致

注意:
- 原地扩。需扩展的空间后方有足够的空间可供扩展,此时,realloc 函数直接在原空间后方进行扩展,并返回该内存空间首地址(即原来的首地址)。
- 异地扩。需扩展的空间后方没有足够的空间可供扩展,此时,realloc 函数会在堆区中重新找一块满足要求的内存空间,把原空间内的数据拷贝到新空间中,并主动将原空间内存释放(即还给操作系统),返回新内存空间的首地址。
- 扩容失败。需扩展的空间后方没有足够的空间可供扩展,并且堆区中也没有符合需要开辟的内存大小的空间。结果就是开辟内存失败,返回一个 NULL(空指针)。
4)free
free 函数的作用就是将 malloc、calloc 以及 realloc 函数申请的动态内存空间释放,其释放空间的大小取决于之前申请的内存空间的大小。
3.C++内存管理方式
C 语言内存管理方式在 C++ 中可以继续使用但C++有更简单的用法:
通过 new 和 delete 操作符进行动态内存管理。
3.1new和delete的基本操作
(1)new 一个 int 类型的对象
int main()
{// 用malloc动态申请一个int类型的空间int* p1 = (int*)malloc(sizeof(int));// 销毁p1free(p1);// 动态申请一个int类型的空间int* p1 = new int;// 销毁p1delete p1;return 0;
}
(2)new 10 个 int 类型的对象
int main()
{// 用malloc动态申请一个int类型的空间int* p2 = (int*)malloc(10 * sizeof(int));// 销毁free(p2);// 动态申请一个int类型的空间int* p2 = new int[10];// 销毁delete[] p2;return 0;
}
delete[ ] 对应的是new[ ] ,是申请多个空间时的样子。
(3)new 一个 int 类型对象,然后初始化为 10
{int* p3 = (int*)malloc(sizeof(int));*p3 = 10; //赋值//销毁free(p3);// 动态申请一个int类型的空间并初始化为10int* p3 = new int(10);// 销毁delete p3;return 0;
}
(4)new 10 个 int 类型对象,并进行初始化
int main()
{//动态申请10个int类型的空间并初始化为1到10int* p8 = (int*)malloc(sizeof(int) * 10); //申请for (int i = 0; i < 10; i++) //赋值{p8[i] = i;}free(p8); //销毁//动态申请10个int类型的空间并初始化为1到10int* p4 = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//销毁p4delete[] p4;return 0;
}
当然如用new在赋值的时候,如果只给前面的控件赋值,后面的空间会赋值为0

总结如下图:

- 申请和释放 单个 元素的空间,使用 new 和 delete
- 申请和释放 连续 的空间,使用 new[ ] 和 delete[ ]
3.2new和delete操作符自定义类型
对于内置类型来说,malloc 和 new 用法几乎一样,
但是对于自定义类型来说,new和delete相比于malloc,会调用构造函数和析构函数
这里以链表为列,看看malloc和new的区别
malloc:
//链表
struct ListNode
{ListNode* next;int val;
};//申请节点
struct ListNode* BuyListNode(int x)
{struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));assert(node);node->next = NULL;node->val = x;return node;
}int main()
{// 定义n1节点struct ListNode* n1 = BuyListNode(1);free(n1);return 0;
}
new:
//链表
class ListNode
{
public://构造函数ListNode(int val = 0):_next(nullptr), _val(val) // 初始化列表{cout << "ListNode" << endl;}~ListNode(){cout << "~ListNode" << endl;}
private:ListNode* _next;int _val;
};int main()
{// 定义n1节点ListNode* n2 = new ListNode(2); // new会去调用ListNode的构造函数delete n2;return 0;
}

3.3总结:
- 1)malloc/free是函数,而new/delete是关键字
- 2)C++ 中如果是申请内置类型的对象或是数组,用 new/delete 和 malloc/free 没有什么区别。
- 3)如果是自定义类型的话,new 和 delete 分别是 开空间+构造函数、析构函数+释放空间,而 malloc 和 free 仅仅是 开空间和释放空间,可以看到区别还是很大的。
- 4)建议在 C++ 中无论是内置类型还是自定义类型的申请和释放,尽量都使用 new 和 delete。
4.operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数
new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

operator new 实际也是通过 malloc 来申请空间,如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
operator delete 最终是通过 free 来释放空间的。
5.new和delete的实现原理
5.1内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.2自定义类型
1)new的原理
- 1. 调用operator new函数申请空间
- 2. 在申请的空间上执行构造函数,完成对象的构造
2)delete的原理
- 1. 在空间上执行析构函数,完成对象中资源的清理工作
- 2. 调用operator delete函数释放对象的空间
3)new T[N] 的原理
- 1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 2. 在申请的空间上执行N次构造函数
4)delete[ ] 的原理
- 1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
6.常见问题:
1)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在释放空间前会调用析构函数完成空间中资源的清理
2)内存泄漏
什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
3)内存泄漏分类
C/C++程序中一般我们关心两种方面的内存泄漏:
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。
假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏(Heap leak)
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
4)如何避免内存泄漏
(1)工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
ps:这个是理想状态,但是如果碰上异常时,就算注意释放了,还是可能会出问题。可能
要智能指针来管理才有保证。
(2)采用 RAII 思想或者智能指针来管理资源。
(3)有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
(4)出问题了使用内存泄漏工具检测。(ps:不过很多工具都不够靠谱,或者收费昂贵)。
相关文章:
C++好难(5):内存管理
这一节学完,我们 C嘎嘎 就算是正式入门了,但是之后的课还会更上一阶d(ŐдŐ๑) 继续坚持! 【本节目标】 1. C/C内存分布 2. C语言中动态内存管理方式 3. C中动态内存管理 4. operator new与operator delete函数 5. new和delete的实现原…...
vue-admin-template中vue动态路由不显示问题解决
使用的的是vue-admin-template,这是一个极简的 vue admin 管理后台,它只包含了 Element UI & axios & iconfont & permission control & lint,这些搭建后台必要的东西。需要根据自己的需求二次开发。 线上地址:vue-admin-tem…...
IP协议介绍
文章目录 一、IP协议的基本认识二、IP的协议头格式三、网段划分四、特殊的IP地址五、IP地址的数量限制六、私有IP地址和公网IP地址 一、IP协议的基本认识 IP在网络分层中属于网络层协议,传输层协议里的TCP协议解决的是可靠性问题,网络层协议里的IP协议能…...
将一个单体服务重构成微服务
将一个单体服务重构成微服务需要经过以下步骤: 1. 拆分服务:将单体服务拆分成多个小服务,每个服务只负责一个特定的功能。拆分的原则是将服务按照业务功能进行划分,每个服务都应该是相对独立的。 2. 设计API:为每个服务…...
SpringBoot项目如何打包成exe应用程序
准备 准备工作: 一个jar包,没有bug能正常启动的jar包 exe4j,一个将jar转换成exe的工具 链接: https://pan.baidu.com/s/1m1qA31Z8MEcWWkp9qe8AiA 提取码: f1wt inno setup,一个将依赖和exe一起打成一个安装程序的工具 链接:…...
一文读懂:客户管理系统平台是什么?有什么作用?
“客户管理系统平台是什么?” “客户管理系统平台有什么作用?在哪里可以应用?怎么用?” 经常可以听到企业内部关于客户管理系统平台的这些问题,本文将会为您一一解答: 一、客户管理系统平台是什么 顾名…...
Node.js 与 TypeScript
目录 1、什么是 TypeScript 2、运行TypeScript 3、TypeScript 在Node.js 生态中的情况 1、什么是 TypeScript TypeScript是一种流行的开源语言,由微软维护和开发。它受到了世界各地许多软件开发人员的喜爱和使用。 基本上,它是JavaScript的超集&…...
Python并发编程之进程理论
前言 本文将详细介绍进程相关概念。 进程和程序 计算机上的未运行的QQ、Wechat等都属于程序,但是一旦当这些程序运行起来的话,就可以被称为进程。因此可以如下定义程序和进程: 程序:就是存在硬盘上的一堆代码。 进程…...
超级详细的mysql数据库安装指南
MySql数据库 如果你的电脑是mac那么你看这位大佬的分享。 如果你的电脑是windows,参考下面的安装步骤。 一、下载mysql数据库? 进入MySQL官方网站(MySQL Community Downloads),按下图顺序点击 1、进入下载页面 2、…...
Java并发编程实践学习笔记(三)——共享对象之发布和异常
目录 1 公共静态变量逸出 2 非私有方法逸出私有变量 3 this引用逸出 4 构造函数中的可覆盖方法调用逸出 发布(publishing)一个对象的意思是:使对象能够在当前作用域之外的代码中使用。例如,将一个指向该对象的引用保存到其他代…...
Python学习之Image模块图片滤镜效果操作示例
前言 滤镜效果是图像处理中常用的一种技术,可以用来增强图像的视觉效果,实现不同的效果,比如增强对比度、饱和度、色彩等。滤镜效果可以帮助用户快速地调整图像的特性,从而使图像更加适合用户的需求。 Image模块对于图像处理的…...
Grafana 系列-统一展示-5-AWS Cloudwatch 仪表板
系列文章 Grafana 系列文章 👍️强烈推荐 强烈推荐使用 GitHub 上的 monitoringartist/grafana-aws-cloudwatch-dashboards 仪表板。该 repo 有一系列 AWS 资源的仪表板,包括但不限于: EC2EBSAPI GWAutoscalingBillingEKSLambdaLogsRDSS3…...
MySQL---控制流函数、窗口函数(序号函数、开窗聚合函数、分布函数、前后函数、头尾函数、其他函数)
1. 控制流函数 格式 解释 案例 IF(expr,v1,v2) 如果表达式 expr 成立,返回结果 v1;否则,返回结果 v2。 SELECT IF(1 > 0,正确,错误) ->正确 IFNULL(v1,v2) 如果 v1 的值不为 NULL,则返回 v1ÿ…...
一心报国的西工大网安人走出新手村
大二下学期5月5日晚上,西工大长安校区教学西楼,作为一名网安专业本科生,从大一便立志学好网安知识,报效祖国,却苦于没有优秀学习资源,就把这事儿拖到了大二,最近上了一门专业课,如同…...
如何安装oracle的sample schema
首先从如下的地址选择合适的版本进行下载 https://github.com/oracle-samples/db-sample-schemas/releases 如果是rac环境,最好是将这个数据库停掉,然后只启动一个instance,然后再开始安装 [Tue May 09 20:26:34][377951][oraclenshqae01adm…...
ChatGPT :国内免费可用 ChatGPT +Midjourney绘图
前言 ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI 研发的聊天机器人程序 ,于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来…...
女孩子转数据分析难吗?难在哪里?
对于数据分析,很多人乍一听会觉得没啥技术难度,是个适合女孩子的专业。我们面对很多零基础小白也是用通俗的语言来形容这个专业:一般是通过Excel或者power BI工具对数据进行分析,制作成可视化的报表给领导层,为公司业务…...
基于常用设计模式的业务框架
前言 做开发也有好几年时间了,最近总结和梳理自己在工作中遇到的一些问题,工作中最容易写出BUG的需求就是改造需求了。一个成熟的业务系统是需要经过无数次迭代而成的,也意味着经过很多开发人员之手,最后到你这里,大部…...
ubuntu重启ssh服务
一、开启ssh服务首先需要安装打开ssh服务的库: sudo apt-get install openssh-server 二、检查当前的ssh开启情况: ps -e |grep ssh 三、如果有sshd,则ssh-server已经启动;若仅有agent,则尚未启动; 开启ssh…...
【19】SCI易中期刊推荐——计算机 | 人工智能领域(中科院2区)
💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
STM32---外部32.768K晶振(LSE)无法起振问题
晶振是否起振主要就检查两个1、晶振与MCU是否兼容;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容(CL)与匹配电容(CL1、CL2)的关系 2. 如何选择 CL1 和 CL…...
Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
JavaScript 标签加载
目录 JavaScript 标签加载script 标签的 async 和 defer 属性,分别代表什么,有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...
