C++初阶:内存管理
目录
- 1. C/C++中各种资源的内存分布
- 1.1 C/C++程序内存区域划分
- 1.2 各资源的内存分布情况(练习)
- 2. C++中的动态内存管理方式
- 2.1 new/delete开辟内置类型空间
- 2.2 new/delete开辟销毁自定义类型空间
- 3. operator new 与 operator delete函数
- 4. new与delete的实现原理
- 5. 定位new表达式与池化计数
- 6. malloc/free与new/delete的异同
1. C/C++中各种资源的内存分布
1.1 C/C++程序内存区域划分
正在执行的程序是在计算机的内存空间上运行的,C/C++为了程序的高效运行,将内存划分了多个区域来进行对不同特性种类资源的区别管理。
1.2 各资源的内存分布情况(练习)
//全局变量,数据段
int globalVar = 1;//静态全局变量,数据段
static int staticGlobalVar = 1;
void Test()
{//静态变量,数据段static int staticVar = 1;//局部变量,栈int localVar = 1;//局部变量,数组,栈//sizeof(num1),代表整个数组,40字节int num1[10] = { 1, 2, 3, 4 };//局部变量,字符数组,栈//sizeof(char2),代表整个数组,5字节//strlen(char2),字符串长度,4char char2[] = "abcd";//sizeof(pChar3),指针,4/8字节//strlen(pChar3),字符串长度,4//只读字符串,代码段const char* pChar3 = "abcd";//动态开辟空间,堆//ptr1指针,4/8字节int* ptr1 = (int*)malloc(sizeof(int) * 4);//calloc会用0进行申请空间的初始化int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}
2. C++中的动态内存管理方式
- 在编写程序时,我们时常需要一段可以动态增长我们可以控制其开辟与销毁的空间,C语言中我们学习过在内存中动态开辟空间的方式,我们通过malloc与free来进行空间开辟与销毁。
- 而C++中有新的动态开辟空间的方法,new与delete,它们在使用上更加方便,且可以应用于更广泛的场景,接下来,就让我们来进行对其的学习与使用。
2.1 new/delete开辟内置类型空间
new:
//开辟一个指定类型大小的空间
int* ptr1 = new int;//为开辟的空间赋于指值
int* ptr2 = new int(10);//开辟连续n个指定类型大小的空间
int* ptr3 = new int[10];//连续空间的初始化赋值
int* ptr4 = new int[3]{1,2,3};
delete:
//释放大小为1个指定类型大小的空间
delete ptr1;//释放大小为多个指定类型空间大小的空间
delete[] ptr3
注: new/delete 与 new[]/delete[] 必须配合使用,不能混用
2.2 new/delete开辟销毁自定义类型空间
- new/delete开辟自定义类型的动态空间,会自动调用自定义类型的构造与析构函数
class A
{
private:int _a;
public:A(int a = 0):_a(a){cout << "A()" << endl;}A(const A& tmp){_a = tmp._a;cout << "A(const A&)" << endl;} ~A(){cout << "~A()" << endl;}
};int main()
{A* pa1 = new A;delete pa1;A* pa2 = new A[3];delete[] pa2;return 0;
}
- 开辟自定义类型空间的初始化方式
A aa1;
A aa2;
A aa3;//一段空间
//方法1:(用存在的对象)
A* pa1 = new A(aa1);
//方法2:(创建匿名对象)
A* pa2 = new A(A());
//方法3:(隐式类型转换,构造 + 拷贝构造,优化为构造)
A* pa3 = new A(3);//多段空间
//方法1:
A* pa4 = new A[3]{aa1, aa2, aa3};
//方法2:
A* pa5 = new A[3]{A(), A(), A()};
//方法3:(前三个元素初始化为1,2,3,后面会赋值的部分会全部默认初始化为0,类似数组)
A* pa6 = new A[10]{1, 2, 3};
3. operator new 与 operator delete函数
- new和delete是我们进行动态内存申请和释放的操作符,而其实现空间的开辟与释放的方式为,去调用用名为operator new 和operator delete的两个函数。
- 这两个函数是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间,接下来就让我们来学习这两个函数的相关知识。(虽然名为operator,但与运算符重载无关,两者为全局函数)
- operator new与operator delete的实现,底层为直接调用malloc与free来实现空间的动态开辟,其用法也与malloc/free相同。(operator new与operator delete两者可以直接调用)
int* pa = (int*)operator new(sizeof(int));*pa = 10;
cout << *pa << endl;operator delete(pa);
- 既然operator new/delete实现依旧是调用malloc/free实现动态开辟空间,那为什么不去直接使用malloc/free?
- malloc申请空间失败会返回0(NULL),此返回值不符合面向对象的编程特性,所以C++对其进行了一层封装,使得申请空间失败后抛出异常,为了与malloc的封装匹配,于是将free也进行了封装,封装为operator delete。
- 调用抛出异常演示:
//连续申请空间,申请空间不足,开辟失败
void func_test()
{char* c1 = new char[1024 * 1024 * 1024];//cout << c1 << endl;//char类型的变量,流插入操作自动识别时会默认识别为字符串而不是指针cout << (void*)c1 << endl;//捕获异常的操作会改变执行流,不再执行下面操作,而会抛出异常//类似gotochar* c2 = new char[1024 * 1024 * 1024];cout << (void*)c2 << endl;
}int main()
{try{func_test();}catch (const exception& e){cout << e.what() << endl;}return 0;
}
4. new与delete的实现原理
- 申请开辟自定义类型空间时,new操作符会先开辟出指定大小的空间,而后调用构造函数初始化开辟出的空间。(申请空间 + 调用构造)
- delelte销毁自定义类型的申请空间时,会先调用自定义类型的析构函数,而后再进行空间的销毁释放。(调用析构,销毁空间)
- new/delete操作符在编译时会直接按照上述调用步骤,生成汇编指令。(汇编调试,call,jump)
- new调用new,申请时,会先开辟空间,再调用构造,构造时再调用new。销毁时,会先调用析构,析构先销毁里层new申请的空间,而后再销毁外层new申请的空间。
class Stack
{
private:int* _a;int _capacity;int _top;
public:Stack(int n = 4){cout << "Stack()" << endl;_a = new int[n];_top = 0;_capacity = 4;}~Stack(){cout << "~Stack()" << endl;delete[] _a;_top = 0;_capacity = 0;}
};int main()
{Stack* p1 = new Stack;delete[] p1;return 0;
}
- new/delete与new[]/delete[]不匹配使用可能会产生的风险与delete[]的工作原理:
(汇编调试)
int main()
{Stack* p1 = new Stack[10];delete[] p1;return 0;
}
- 10个Stack类型的变量组成的空间大小应为120字节,可是,经过汇编调试后,大小却为124字节。这是因为,new[]申请连续的自定义类型空间时,会额外在头部申请一个四字节大小的空间用来存放申请的自定义类型空间个数,delete[]在析构时会向前调整四个字节,读取需要调用析构的次数。(delete获取需要调用析构的次数)
- 当使用delete去释放new[] 出的空间时,不会向前调整四个字节,而是调用一次析构函数后,从第一个元素的首地址释放空间,会导致内存泄漏。
- 编译器的优化:因为类A的成员变量只有一个int变量,且构造时没有开辟额外空间,当我们将类A的析构函数屏蔽后(没有使用析构的必要),当再次new[]一段连续空间时,将不再于头部开辟额外空间存储元素个数。delete所要做的就只是释放开辟的空间,这一点,使用free也同样可以做到。
class A
{
private:int _a;
public:A(){cout << "A()" << endl;}/*~A(){cout << "~A()" << endl;}*/
};int main()
{A* pa = new A[10];//用delete去销毁new[]申请的空间delete pa;//free(pa);return 0;
}
5. 定位new表达式与池化计数
- 在前面的学习中我们了解到,类的析构函数可以显示调用,而构造函数却不可以。那么,当我们不使用new的方式开辟出自定义类型的空间后,有没有办法对这段空间使用构造函数进行初始化呢,接下来,我们引入定位new表达式。
//调用默认构造
new(指针:指定空间地址)类型
//赋予初始值
new(指针)类型(初始值)
示例:
class A
{
private:int _a;
public:A(int a = 0):_a(a){cout << "A()" << endl;}~A(){cout << "~A()" << endl;}
};int main()
{A* pa1 = (A*)operator new(sizeof(A));//调用构造new(pa1)A;//析构pa1->~A();//释放空间operator delete(pa1);A* pa2 = (A*)operator new(sizeof(A));//调用构造,并指定初始值new(pa1)A(10);pa2->~A();operator delete(pa2);return 0;
}
- 定位new应用场景:
<1> 因为new申请动态开辟的空间是在堆上,很多时候我们需要频繁的调用申请空间,这样的效率非常低。
<2> 所以,C++中创建了内存池来应对这一类问题,先提前申请出一大块空间备用,这块空间被称为内存池,当我们需要申请空间时,不用再去堆上申请,而是可以直接找内存池申请划分。
<3> 这些申请来的空间(自定义类型)是已经开辟好的,这些空间没有调用构造函数进行初始化,无法使用,而定位new就可以解决这样的问题。
6. malloc/free与new/delete的异同
相同点:
- 都是于堆区上开辟空间,且都需要手动释放
不同点:
- malloc申请的空间不会进行初始化,而new申请出的空间可以进行初始化
- malloc申请空间需要计算空间大小,而new只需要指定对象个数
- malloc的返回必须要强转为对应类型的指针,而new在申请空间时就声明了类型
- malloc申请空间失败返回空,new申请失败抛出异常
- malloc开辟销毁空间时不会调用构造,析构函数,而new/delete开辟销毁空间时会调用构造与析构
相关文章:

C++初阶:内存管理
目录 1. C/C中各种资源的内存分布1.1 C/C程序内存区域划分1.2 各资源的内存分布情况(练习) 2. C中的动态内存管理方式2.1 new/delete开辟内置类型空间2.2 new/delete开辟销毁自定义类型空间 3. operator new 与 operator delete函数4. new与delete的实现…...
vue和react的diff算法源码
Vue.js 中的虚拟 DOM Diff 算法是其性能优化的关键之一。 Vue.js 的 Diff 算法主要基于 Snabbdom,以下是 Vue.js 中虚拟 DOM Diff 算法的简化版伪代码,以便说明其基本思想: function patch(oldVnode, vnode) {// 如果 oldVnode 不存在&…...

Coordinate Attention(CVPR 2021)
paper:Coordinate Attention for Efficient Mobile Network Design official implementation:GitHub - houqb/CoordAttention: Code for our CVPR2021 paper coordinate attention 背景 注意力机制,已经被广泛用于提高深度神经网络的性能&…...

计算机网络-第4章 网络层(2)
主要内容:网络层提供的两种服务:虚电路和数据报(前者不用)、ip协议、网际控制报文协议ICMP、路由选择协议(内部网关和外部网关)、IPv6,IP多播,虚拟专用网、网络地址转换NAT,多协议标…...

重学SpringBoot3-WebMvcAutoConfiguration类
更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-WebMvcAutoConfiguration类 是什么什么用生效条件作用 自定义配置的三种方式自定义配置举例1. 自定义 DispatcherServlet 配置2. 静态资源配置3. 自定义…...

探索数据结构:深入了解顺序表的奥秘
✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 1. 什么是顺序表 顺序表是用一段物理地址连续的存储单元依次存储数据元…...

苍穹外卖学习-----2024/03/010---redis,店铺营业状态设置
1.Redis入门 2.在Java中操作Redis 3.店铺营业状态设置 BUG!!! 今天在启动项目时,用到了Redis缓存数据库,但是却出现了报错信息: ERR Client sent AUTH, but no password is set。Caused by: io.lettuce.core.RedisCommandExecutionException…...

RUST 每日一省:发布到crates.io
github是开源代码分享的地方,rust的开源项目除了github,我们还可以将其发布到 crates.io 上,然后其它用户就可以使用cargo进行安装使用了。其实步骤很简单,只有三条命令了,我们一次来看一下。 1、cargo package 首先&a…...

String类及其常用方法
文章目录 1.String类的特性与使用1.1 String类的特性1.2 String对象的创建方式1.3 String 的使用(不同的拼接操作) 2.String常用方法2.1 String的常用方法一2.2 String常用方法二2.3 String常用方法三 1.String类的特性与使用 1.1 String类的特性 Stri…...
1094. 拼车
说在前面 🎈不知道大家对于算法的学习是一个怎样的心态呢?为了面试还是因为兴趣?不管是出于什么原因,算法学习需要持续保持。 题目描述 车上最初有 capacity 个空座位。车 只能 向一个方向行驶(也就是说,不…...

Docker进阶:深入了解容器数据卷
Docker进阶:深入了解容器数据卷 一、前言二、容器数据卷的作用三、容器数据卷的使用方法四、实战--使用docker部署前端项目(数据卷挂载)4.1 重要:准备工作,先在本地创建挂载目录4.2 启动一个临时的nginx容器࿰…...
升级版本彻底解决bootstrap-table-fixed-columns固定列后行对不齐问题
升级到bootstrap-table和bootstrap-table-fixed-columns版本都升级到v1.22.3版本以上,即可解决该问题 bootstrap-table:bootstrap-table/dist/bootstrap-table.min.css at develop wenzhixin/bootstrap-table GitHub bootstrap-table-fixed-columns&…...
打破边界:深入探索STUN在实现无缝NAT穿越和WebRTC通信中的核心作用
引言 STUN是一个网络协议,设计用于帮助在网络地址转换(NAT)后面的设备发现其公网地址和端口号。通过允许这些设备发现自己从外部看到的地址,STUN使得它们能够在NAT或防火墙背后建立端到端的通信,这对于VoIP、视频会议…...
浅谈 前端的动态绑定属性
目录 前言1. 基本知识2. Demo 前言 作为Java开发者,从开发转到全栈,前端好些细节都需要科普,这不就来个动态绑定属性 起因是这个: <uni-tr> <uni-td align"center" :rowspan"checkTypesCount 1"…...
Sklearn支持向量机
支持向量机(Support Vector Machine, SVM)是一种常用的分类算法,它可以用于解决二分类和多分类问题。在Python中,你可以使用Sklearn库来实现SVM。下面是一个简单的例子,展示了如何使用Sklearn进行SVM分类。 # 导入必要…...

【Lazy ORM】 小工具 acw 本地客户端 你负责点击页面,他负责输出代码
介绍 wu-smart-acw-client 简称acw-client,是一个基于Lazy ORM定制的客户端代码生成小工具 Lazy ORM 小工具 acw 本地客户端 你负责点击页面,他负责输出代码安装 <dependency><groupId>top.wu2020</groupId><artifactId>wu-sma…...

《详解:鸿蒙NEXT开发核心技术》
我们现在都知道鸿蒙作为一个国产的全栈自研系统,经过国家主推后。已经引起人们很大的关注,其中作为开发者来说;许多一线大厂已经与其华为鸿蒙展开原生应用的合作了,目前了解到已经有200家。而之后出现了很多的高薪鸿蒙开发岗位&am…...

快速排序 刷题笔记
思路 分治双指针 在每个区间选定一个基准目标 两个指针从数组的两边向中间推进 使用 while循环判断 do {i;}while(q[i]<x); do{j--;}while(q[j]>x); 每次这样做完就会找到q[i]>x,,,,q[j]小于x 此时我们交换 q[i] ,q[j]于是小于x的数分到了小于x的一侧 大…...

DAY by DAY 史上最全的Linux常用命令汇总----man
man是按照手册的章节号的顺序进行搜索的。 man设置了如下的功能键: 功能键 功能 空格键 显示手册页的下一屏 Enter键 一次滚动手册页的一行 b 回滚一屏 f 前滚一屏 q 退出man命令 h 列出所有功能键 /word 搜索word字符串 注意:…...

十六、接口隔离原则、反射、依赖注入
接口隔离原则、反射、特性、依赖注入 接口隔离原则 客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 五种原则当中的i 上一章中的接口,即契约。 契约就是在说两件事,甲方说自己不会多要,乙方会在…...
Java异步编程难题拆解技术
异步编程基础与核心概念 异步编程模型与同步模型的对比 Java中异步编程的常见场景(IO密集型、高并发任务等) 关键术语:Future、CompletableFuture、回调、事件循环 Java异步编程的核心API与框架 Future接口的局限性及基本用法 Completable…...

ideal2022.3.1版本编译项目报java: OutOfMemoryError: insufficient memory
最近换了新电脑,用新电脑拉项目配置后,启动时报错,错误描述 idea 启动Springboot项目在编译阶段报错:java: OutOfMemoryError: insufficient memory 2. 处理方案 修改VM参数,分配更多内存 ❌ 刚刚开始以为时JVM内存设置…...
IOS 打包账号发布上传和IOS Xcode证书配置
xcode下载 https://developer.apple.com/download/all/ App发布 https://appstoreconnect.apple.com/ https://appstoreconnect.apple.com/teams/83ba877c-af24-4fa5-aaf2-e9b9b6066e82/apps/6473148620/testflight/groups/eb983352-b2e2-4c29-bbb7-071bf7287795 https://devel…...

3. 简述node.js特性与底层原理
😺😺😺 一、Node.js 底层原理(简化版) Node.js 是一个 基于 Chrome V8 引擎构建的 JavaScript 运行时,底层核心由几部分组成: 组成部分简要说明 1.V8 引擎 将 JS 编译成机器码执行࿰…...
C++ const 修饰符深入浅出详解
C const 修饰符深入浅出详解 📅 更新时间:2025年6月6日 🏷️ 标签:C | const关键字 | 常量 | 多文件编程 | 现代C 文章目录 前言🌟 一、const 是什么?为什么要用?示例✅ const 的四大好处 &…...

【C语言】C语言经典小游戏:贪吃蛇(下)
文章目录 一、游戏前准备二、游戏开始1、游戏开始函数(GameStart)1)打印欢迎界⾯(WelcomeToGame)2)创建地图(CreateMap)3)初始化蛇⾝(InitSnake)4…...
场景题-3
如何实现一个消息队列 拆解分析主流的几种消息队列 1、基本架构 生产者Producer、消费者Consumer、Broker:生产者发送消息,消费者接受消息,Broker是服务端,处理消息的存储、备份、删除和消费关系的维护。 主题和分区ÿ…...

MySQL体系架构解析(二):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...

WAF绕过,网络层面后门分析,Windows/linux/数据库提权实验
一、WAF绕过文件上传漏洞 win7:10.0.0.168 思路:要想要绕过WAF,第一步是要根据上传的内容找出来被拦截的原因。对于文件上传有三个可以考虑的点:文件后缀名,文件内容,文件类型。 第二步是根据找出来的拦截原…...

008-libb64 你有多理解base64?-C++开源库108杰
正确认识二进制数据和文本数据的关系;深刻理解 base64 编码核心等式:256256256 64646464 经常听到——以至 AI 也会这么回答的:base64 编码用于将二进制数据,转换为文本数据。但是,众所周知,在数字电子计算机中&#…...