C和C++内存管理
C和C++内存管理
- (一)C/C++内存分布
- (二)C语言动态内存管理
- (三)c++内存管理
- (3.1)new/delete操作内置类型
- (3.2)new和delete操作自定义类型
- (四)operator new与operator delete函数
- (五)new与delete的实现原理
- (5.1)内置类型
- (5.2)自定义类型
- (5.3)不匹配使用(面试可能会考)
- (六)定位new表达式(placement-new) (了解)
- (七)malloc/free和new/delete的区别(重点)
(一)C/C++内存分布

我们可以在任务管理器中查看电脑当前进程:

下图是在c语言阶段我们学过的内存区域划分,这一块在C++同样适用:

说明:
- 栈又叫堆栈–非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
- 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段–存储全局数据和静态数据。
- 代码段–可执行的代码/只读常量。
那我们先根据几个题目来回忆我们c语言中内存如何分配的:

前五个比较简单,大家一定不能出错
第一个:A,main函数外定义的变量都是静态的,生命周期都是从程序开始到结束
第二个:A,全局静态一样
第三个:A,由static修饰的也是全局的
第四个:A,没有static修饰的都是局部的
第五个:A,这里的数组名代表整个数组,也是在局部的栈里存放
后面六个难度上升了很多:
第六个:A,char2也是数组,并且我是我们主动申请的,所以也在栈
第七个:A,*char2是解引用,这里的char2代表的首元素地址,首元素a在栈上
第八个:A, pchar3是一个指针,不管它的const修饰指向的内容还是指针,指针都存于栈
第九个:D,*pchar3也是指向第一个元素的地址,但是内容部分用const修饰了,所以在常量区
第十个:A,ptr1是一个指针
第十一个:B,ptr1所指向的内容使我们主动开辟的,所以在堆区。

(二)C语言动态内存管理
C语言中动态内存管理方式:malloc/calloc/realloc/free
这一块在c语言部分讲的很多了,所以我就简单的带大家回忆一下,
malloc:开固定的空间,不初始化。
calloc:开空间,初始化0.
realloc:支持拓展我们原来开的空间。
free:释放我们主动开辟的空间。
【面试题】
- malloc/calloc/realloc的区别?
- malloc的实现原理? 视频链接:https://www.bilibili.com/video/BV117411w7o2/?spm_id_from=333.788.videocard.0
这一块就这样过了,如果这一块还不懂得,建议回去补一下c语言的课。
(三)c++内存管理
(3.1)new/delete操作内置类型
我们简单得来使用一下new和delete
int main()
{int* p1 = new int;int* p2 = new int[10];//简化了c语言sizeof与强转部分delete p1;delete[] p2;//初始化int* p3 = new int(3);int* p4 = new int[10] {0};//全都初始化为0int* p5 = new int[10] {1, 2, 3, 4};//前四个值初始化delete p3;delete[] p4;delete[] p5;return 0;
}

(3.2)new和delete操作自定义类型
class A
{
public:A(int a = 0){_a = a;cout << "A()" << endl;}~A(){cout << "~A()" << endl;}void print(){cout << _a << endl;}
private:int _a;
};int main()
{A* p1 = new A;A* p2 = new A(1);delete p1;delete p2;return 0;
}

大家可以看到c++这块new与delete会自动调用自定义类型的构造和析构函数。
这一块非常的方便我们来演示一下c++里的链表:
struct ListNode
{int _val;ListNode* next;ListNode(int val) :_val(val),next(nullptr){}
};
//c++的struct里的变量与函数均为共有的,这一部分前面讲过
int main()
{ListNode* n1 = new ListNode(1);ListNode* n2 = new ListNode(2);ListNode* n3 = new ListNode(3);ListNode* n4 = new ListNode(4);ListNode* n5 = new ListNode(5);n1->next = n2;n2->next = n3;n3->next = n4;n4->next = n5;return 0;
}
这里是不是相较于c里的初始化啊,插入什么的简单许多。

总之我们只需要记住在c++的使用过程中使用new,delete就行,因为new包含了malloc,delete包含了free。
此外我们在使用new和时是不是与malloc还有一个差别,那就是没有判断是否申请成功,C++引入了抛异常。
抛异常在c++进阶里才会详细的讲解,这里我们简单的演示一下:
int main()
{try{throw try catchvoid* p1 = new char[1024 * 1024 * 1024];cout << "p1" << endl;void* p2 = new char[1024 * 1024 * 1024];cout << "p2" << endl;void* p3 = new char[1024 * 1024 * 1024];cout << "p3" << endl;}catch (const exception& e){cout << e.what() << endl;//发生了什么错误}return 0;
}void func()
{//throw try catchvoid* p1 = new char[1024 * 1024 * 1024];cout << "p1" << endl;void* p2 = new char[1024 * 1024 * 1024];cout << "p2" << endl;void* p3 = new char[1024 * 1024 * 1024];cout << "p3" << endl;
}int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;//发生了什么错误}return 0;
}
上面是在main函数里申请失败,下面是在调用的一个函数里失败。
void func()
{int n = 0;while (1){char* p1 = new char[1024 * 1024];//1024*1024byte是1Mcout << "p1->" << endl;n++;cout << n << endl;;}
}int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;//发生了什么错误}return 0;
}
这个函数就是来看在32位环境下究竟能申请多少MB

大家可以看到32位(总共4G)下大概只能申请1.7G左右,所以堆在内存占大部分。
(四)operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
operator new 与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)//mallocif (_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_FINALLYreturn;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)//free
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
(五)new与delete的实现原理
(5.1)内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
(5.2)自定义类型
(1)new的原理
- 调用operator new函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
(2)delete的原理 - 在空间上执行析构函数,完成对象中资源的清理工作
- 调用operator delete函数释放对象的空间
(3)new T[N]的原理 - 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
- 在申请的空间上执行N次构造函数
(4)delete[]的原理 - 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
(5.3)不匹配使用(面试可能会考)

上面第一组是可以的,第二组不行,我们可以清楚的看到第二组的free少调用了析构函数,这里有内存泄漏的风险。
那我们来看一个神奇的东西:
class A
{
public:A(int a = 0, int b = 0){_a = a;_b = 1;cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;int _b;
};
class B
{
private:int _a;int _b;
};
int main()
{int* p1 = new int[10];//这里没有调用构造函数,实质上只有mallocdelete p1;//这里虽然不匹配,但是内存也不会出现泄漏,实质这里只使用freefree(p1);A* p2 = new A[10];delete p2;//这里有问题B* p3 = new B[10];delete p3;//这里没有问题return 0;
}
大家自己可以去vs里运行一下,那为什么A有问题,B没有问题呢?
首先我们的A与B大小是不是都是8个字节,那我们都调用了10次,那么10个这样的对象是不是80个字节,但是实际上A的10个对象占了84个字节,B的10个对象占了80字节,多的4个字节是用来存储对象的个数。

那为什么A会多开4个字节存储个数呢?
这是因为B没有析构函数,所以编译器就给优化了,严格的来说多存储的个数是给delete后面的[]使用的,当类没有析构函数时,就认为没有开辟的空间需要释放,只要我们写了析构,编译器就不会再有优化,那B的大小就变为84,。
这里总结就是一定要匹配使用,不要错配。
(六)定位new表达式(placement-new) (了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
上面什么意思呢?
就是我们可以只开空间,不初始化,也就是malloc的功能。
class A
{
public:A(int a = 0, int b = 0){_a = a;_b = 1;cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
private:int _a;int _b;
};
int main()
{A* ptr1 = new A;//调用了构造A* ptr2 = (A*)operator new(sizeof(A));//不调构造new(ptr2)A(1, 0);//定位new//下面两步合起来等于上面一步delete ptr1;ptr2->~A();//析构函数是可以显示调用的operator delete(ptr2);//下面两步合起来等于上面一步return 0;
}
大家可能认为这有点脱了裤子放屁对吧,但我想说的是—说得对,但它也有他自己的用途,在生活中99%的场景用不到,需要用的场景是内存池、线程池,连接池,这一块不讲,只是给大家说下有这个东西。

(七)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在释放空间前会调用析构函数完成空间中资源的清理释放
相关文章:
C和C++内存管理
C和C内存管理 (一)C/C内存分布(二)C语言动态内存管理(三)c内存管理(3.1)new/delete操作内置类型(3.2)new和delete操作自定义类型 (四)…...
axios取消请求
1.使用CancelToken: class RequestHttp {service: AxiosInstance;public constructor(config: AxiosRequestConfig) {// 实例化axiosthis.service axios.create(config);/*** description 请求拦截器* 客户端发送请求 -> [请求拦截器] -> 服务器*/this.service.interce…...
阿里中间件——diamond
一、前言 最近工作不忙闲来无事,仔细分析了公司整个项目架构,发现用到了很多阿里巴巴集团开源的框架,今天要介绍的是中间件diamond. 二、diamond学习笔记 1、diamond简介 diamond是一个管理持久配置(持久配置是指配置数据会持久化…...
pyenv -- 一款macos下开源的多版本python环境安装管理工具 国内加速版安装 + 项目venv虚拟环境 pip加速 使用与总结
一个比较方便实用的python多版本环境安装管理工具, 阿里云加速版本 pyenv安装方法: 直接克隆本下面到你的本地目录,然后设置环境变量即可 git clone https://gitee.com/tekintian/pyenv.git ~/.pyenv 环境变量配置 在~/.bash_profile 或者 .zshrc 中增加环境变量 export …...
VitePress 自定义 CSS 指南
VitePress 是一款基于 Vite 和 Vue 3 的静态网站生成器,专为文档编写而设计。尽管 VitePress 提供了丰富的默认主题,但在某些情况下,我们可能需要对其进行更深入的定制以满足特定的视觉需求。本文将详细介绍如何通过覆盖根级别的 CSS 变量来自…...
【舍入,取整,取小数,取余数丨Excel 函数】
数学函数 1、Round函数 Roundup函数 Rounddown函数 取整:(Int /Trunc)其他舍入函数: 2、Mod函数用Mod函数提取小数用Mod函数 分奇偶通过身份证号码判断性别 1、Round函数 Roundup函数 Rounddown函数 Round(数字,保留几位小数)(四…...
无线信道中ph和ph^2的场景
使用 p h ph ph的情况: Rayleigh 分布的随机变量可以通过两个独立且相同分布的零均值、高斯分布的随机变量表示。设两个高斯随机变量为 X ∼ N ( 0 , σ 2 ) X \sim \mathcal{N}(0, \sigma^2) X∼N(0,σ2)和 Y ∼ N ( 0 , σ 2 ) Y \sim \mathcal{N}(0, \sigma^2)…...
HCIA--实验五:静态路由综合实验
静态路由综合实验 一、实验内容: 1.需求/目的: 在ensp模拟器中使用四个路由器,并且在路由器上创建loopback接口,相当于连接了一台主机,通过配置静态路由的方式实现全网通。 二、实验过程 1.道具: 4个…...
不同vlan之间的通信方法
1.通过路由器的物理接口 1.给PC1,PC2配置IP地址,网关2.进入交换机配置vlan,交换机所有口都配置access口并绑定vlan3.配置路由器,进入路由器的两个接口配置网关IP和掩码缺点:成本高,每增加一个vlan就需要一个物理端口和…...
java后端框架
框架就是对技术的封装。 本篇博客小博主首先对以后我们要学习的框架进行简单概述,使大家对框架有一定的基本概念。 一.mybatis mybatis就是对jdbc(数据库连接)进行封装,避免了jdbc中手动设置参数,手动映射结果的操作。…...
如何在Word中插入复选框
如何在Word中插入复选框:详细教程与技巧 在Word中插入复选框是一项非常实用的技巧,尤其是在制作问卷调查、待办事项清单、交互式表单或文档中需要用户进行选择时,复选框不仅能提高文档的功能性,还能显得更加专业。本文将详细讲解…...
Android 源码中jni项目 加载so目录小结
Android 源码中jni项目 加载so目录小结 文章目录 Android 源码中jni项目 加载so目录小结一、前言二、so目录验证测试1、jni so文件错误报错(1)报错1 - 未找到so文件:(2)报错2 - so文件中未找到native方法: …...
24/9/6算法笔记 kaggle 房屋价格
预测模型主要分为两大类: 回归模型:当你的目标变量是连续的数值时,你会使用回归模型进行预测。回归模型试图找到输入特征和连续输出之间的关联。一些常见的回归模型包括: 线性回归(Linear Regression)岭回归…...
【MA35D1】buildroot 编译使用经验
文章目录 芯片介绍Buildroot开发Linux实践环境搭建代码获取编译执行步骤(仅适用于我公司产品) 后续有需要更改的输出文件目录 芯片介绍 NuMicro MA35D1系列为一颗异核同构的多核心微处理器,适用于高端 Edge IIoT Gateway。它是基于双核 64 位…...
排查 MyBatis XML 配置中的 IF 语句与传值名称不匹配的 Bug
文章目录 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都是在得 前言,在改一个bug得时候发现一个有意思得问题,就是myb…...
数字证书与公钥基础设施
关注这个证书的其他相关笔记:NISP 一级 —— 考证笔记合集-CSDN博客 0x01:数字证书 数字证书是由第三方可信机构(一般是证书服务器)颁发的数字证书,可以证明身份的可信度。 数字证书具有以下特点以及性质:…...
拥抱数智化,JNPF低代码平台如何推动企业转型升级
随着信息技术的飞速发展,企业面临的市场竞争日益激烈,传统的业务流程和管理模式已经难以满足快速变化的市场需求。数智化转型成为企业持续发展的必由之路。在这一过程中,低代码开发平台扮演了至关重要的角色。本文将探讨JNPF低代码平台如何助…...
Linux shell脚本 (十二)case语句_linux awk case语句
case工作方式如上所示。取值后面必须为关键字 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。 取值将…...
【二等奖成品论文】2024年数学建模国赛B题25页成品论文+完整matlab代码、python代码等(后续会更新)
您的点赞收藏是我继续更新的最大动力! 一定要点击如下的卡片,那是获取资料的入口! 【全网最全】2024年数学建模国赛B题31页完整建模过程25页成品论文matlab/python代码等(后续会更新「首先来看看目前已有的资料,还会…...
国内快速高效下载 HuggingFace上的各种大语言模型
预先安装: apt install aria2 # sudo apt install aria2apt install git-lfs # sudo apt install git-lfs下载hfd wget https://hf-mirror.com/hfd/hfd.shchmod ax hfd.sh设置环境变量 Linux export HF_ENDPOINThttps://hf-mirror.comWindows $env:HF_ENDPOINT…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
