C/C++【内存管理】

news2024/11/24 9:20:30

✨个人主页: Yohifo
🎉所属专栏: C++修行之路
🎊每篇一句: 图片来源

  • Love is a choice. It is a conscious commitment. It is something you choose to make work every day with a person who has chosen the same thing.

    • 爱是一种选择。这是一种有意识的承诺。这是你选择每天与一个选择同样事情的人一起工作的东西。

    配图


文章目录

  • 📘前言
  • 📘正文
    • 📖内存分布
      • 🖋️五大分区
      • 🖋️图解
    • 📖重温
      • 🖋️malloc/calloc/realloc
      • 🖋️free
    • 📖初识
      • 🖋️new
      • 🖋️delete
      • 🖋️特点
    • 📖探究
      • 🖋️封装实现
      • 🖋️代码展示
    • 📖new/delete 实现步骤
      • 🖋️内置类型
      • 🖋️自定义类型
    • 📖定位new
      • 🖋️应用场景
    • 📖注意事项
  • 📘总结


📘前言

C++中的内存管理机制和C语言是一样的,但在具体内存管理函数上,C语言malloc已经无法满足C++面向对象销毁的需求,于是祖师爷在C++中新增了一系列内存管理函数,即 newdelete
著名段子:如果你还没没有对象,那就尝试 new 一个吧

致敬C语言


📘正文

将内存分成不同区域是为了实现更好的管理,比如在我们现实生活中,一幢房子会被分为客厅、厨房、卧室、卫生间等区域,目的是为了使我们生活更加方便、空间利用更加合理,计算机也是如此,更何况是空间非常珍贵的内存,因此在我们的程序中存在不同内存分区

房子分区

📖内存分布

在程序中存在五大分区,各个分区各司其职,比如我们耳熟能详的栈区、堆区、静态区

🖋️五大分区

栈区:

  • 又称做堆栈,用于存储非静态局部变量、函数参数、返回值等,的空间是向下增长的

内存映射段:

  • 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信

堆区:

  • 用于程序运行时动态内存分配,堆是可以上增长的,我们的动态内存就是在上申请的

数据段:

  • 数据段中负责存储全局数据和静态数据

代码段:

  • 代码段中存储可执行的代码/只读常量

注意: 内存中还存在内核空间,但我们普通用户代码无法读写

🖋️图解

通过具体代码展示具体分布如下

图示

📖重温

先简单回顾下C语言中的动态内存管理

🖋️malloc/calloc/realloc

C语言提供了三种动态内存管理函数

malloc:申请指定大小的空间

int* pi = (int*)malloc(sizeof(int) * 1);	//申请一个整型
double* pd = (double*)malloc(sizeof(double) * 2);	//申请两个浮点型
char* pc = (char*)malloc(sizeof(char) * 3);	//申请三个字符型

注意: malloc申请的空间都是未初始化的,即被编译器置为随机值

calloc:将申请的空间初始化为 0

int* pi = (int*)calloc(1, sizeof(int));	//申请一个整型
double* pd = (double*)calloc(2, sizeof(double));	//申请两个个浮点型
char* pc = (char*)calloc(3, sizeof(char));	//申请三个字符型

注意: calloc参数列表与malloc不同,同时calloc申请的空间会被初始化为 0

realloc:对已申请的空间进行扩容

int* tmp = (int*)realloc(pi, sizeof(int) * 10);	//将 pi 扩容为十个整型
pi = tmp;	//常规使用方法

注意: 我们要对所有的申请函数进行空指针检查,预防野指针问题

堆区的空间由我们管理,编译器很信任我们,因此我们要做到有借有还,再借不难

凡是动态开辟的空间,用完后都需要释放

🖋️free

C语言提供的空间释放函数是free

free(tmp);	//此时tmp指向pi扩容后的空间,释放tmp就行了
tmp = pi = NULL;	//两者都需要置空
free(pd);
pd = NULL;
free(pc);	//只要是动态开辟的,都需要通过 free 释放
pc = NULL;

注意: 只有动态开辟的空间才能使用 free,同时一块空间不能释放两次。我们在 free 后通常会把指针置空

关于C语言动态内存管理更多细节可以看看这篇文章:《C语言动态管理》
这里就不再阐述

C语言 中管理函数只能对内置类型使用,而 C++ 中存在很多自定义类型,常规 malloc 等函数无能为力

📖初识

出现了新的关键字:newdelete,它们也有很多形式和使用细节

🖋️new

使用:

int* pi = new int;	//申请一个整型
double* pd = new double(3.14);	//申请一个浮点型并初始化为 3.14
char* pc = new char[5] {'H', 'e', 'l', 'l', 'o'};	//申请五个字符型,并分别初始化为 Helloc

注意:

  • newmalloc等不同,不需要进行空指针检查,也不需要进行类型转换
  • new 的使用极其简单

特点:

  • new可以用于自定义类型
  • 动态开辟时,会调用自定义类型的构造函数
//假设存在日期类
Date* ptr = new Date[5];	//申请五个日期类,这些类都在堆上

下面来看看C++中的内存释放函数

🖋️delete

形式:

  • delete 指针
  • delete[] 指针

使用:

  • C语言中的 free 可以用于释放所有动态申请函数,而 C++ 不行,申请与释放需要配套使用
int* pi = new int;
delete pi;	//直接释放

double* pd = new double[5];
delete[] pd;	//释放五次

Date* ptr = new Date[5];
delete[] ptr;	//释放五次,即调用五次日期类的析构函数

注意:

  • 需要配套使用,new int 搭配 delete,而 new int[] 需要搭配 delete[]

特点:

  • delete可以用于自定义类型
  • 调用销毁时,会先调用自定义类型的析构函数

🖋️特点

C语言C++动态内存管理函数的最大区别是: 是否会调用自定义类型的构造函数和析构函数

C语言明显不会,毕竟那时候还没有这些概念,而 C++ 作为面向对象的语言,调用构造与析构函数是必然的

C语言中的申请函数不能通过C++的释放函数进行释放,同理C++的申请空间也不能通过C语言的释放函数进行释放,比如下面这些情况是不合理的,可能引发问题

int* cPi = (int*)malloc(sizeof(int));
delete cPi;	//不合理的操作

int* cppPi = new int;
free(cppPi);	//这样也是不合理的

Date* ptr = new Date;
free(ptr);	//此时会报错,因为 free 并不会调用析构函数

切记,申请与释放要配套使用

📖探究

为何C++中的动态内存管理函数能做到调用构造析构函数呢?

  • 这是因为我们也是调用的其他函数,正是得益于C++中的封装

🖋️封装实现

newdelete 是用户进行动态内存申请和释放的 操作符,它们在实现时会去调用真正的全局函数 operator newoperator delete,具体调用情况如下所示:

newnew []

  • new 调用 operator new
  • new [] 调用 operator new[]

deletedelete []

  • delete 调用 operator delete
  • delete [] 调用 operator delete[]

注意: operator new[] 最终是调用 operator newoperator delete[] 最终也是调用 operator delete
所以严格来说,operator newoperator delete 才是我们探讨的主角

🖋️代码展示

先来看看 operator new 的代码实现

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECLoperatornew(size_tsize)_THROW1(_STDbad_alloc)
{
	// try to allocate size bytes
	void* p;
	while ((p = malloc(size)) == 0)
		if (_callnewh(size) == 0)
		{
			// report no memory
			//如果申请内存失败了,这里会抛出bad_alloc类型异常
			staticconststd::bad_allocnomem;
			_RAISE(nomem);
		}
	return(p);
}
//代码源自:比特教育科技 https://www.bitejiuyeke.com/index

可以看到,其实 operator new 就是通过对 malloc 的封装实现的,不过进行了改进,当对象为自定义类型时,会去调用它的构造函数,并且当开辟失败时,会抛出异常

再来看看 operator delete 的代码实现

/*
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)
//代码源自:比特教育科技 https://www.bitejiuyeke.com/index

operator delete 的代码中也有 free 的影子,当释放对象为自定义类型时,会调用它的析构函数

📖new/delete 实现步骤

下面再来看看两者具体的实现步骤

🖋️内置类型

对于内置类型来说,使用 malloc/free 和 new/delete 没什么区别

🖋️自定义类型

对于自定义类型,new/delete 的实现步骤如下:

new

  • 调用 operator new 申请空间
  • 在申请的空间上调用构造函数

delete

  • 在空间上调用析构函数
  • 再调用 operator delete 释放空间

new []

  • 调用 operator new[] 函数,根据数值N,调用N次 operator new 函数申请空间
  • 在申请的空间上调用N次构造函数

delete []

  • 在申请的空间上调用N次析构函数
  • 调用 operator delete[] 函数,然后由函数再调用 operator delete 释放空间

📖定位new

定位newnew 的新用法

目的:

  • 对已开辟而未初始化的空间进行初始化

形式:

  • new(指针)构造函数
//定位new
Stack* ptr = (Stack*)malloc(sizeof(Stack));	//malloc 不会调用构造函数,此时未初始化

new(ptr)Stack();	//通过定位new初始化对象

🖋️应用场景

定位new 可以用在内存池这个项目中

  • 向堆中申请一块定额空间,此时空间未初始化
  • 此时就需要通过 定位new 来进行初始化
    内存池

📖注意事项

开辟与释放需要配对使用

malloc/calloc/realloc 搭配 free

new 搭配 delete

new [] 搭配 delete []


📘总结

以上就是关于 C++ 内存管理的全部内容了,记住一点就够了:认识 new ,配对使用。

如果你觉得本文写的还不错的话,可以留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


星辰大海

相关文章推荐
类和对象实操
类和对象实操之【日期类】

===============

类和对象合集系列
类和对象(下)
类和对象(中)
类和对象(上)

===============

C++入门必备
C++入门基础

感谢支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/346615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

商城系统必备营销工具(五)——积分商城

做商城,流量必不可少,日活跃度也很重要。现在各大APP、网站、小程序和微商城,基本都在为了巩固流量做积分商城,虽然已经随处可见,但很多企业商家却并没有将积分商城运作起来,积分商城也没有人浏览兑换商品。…

跟ChatGPT,聊聊ChatGPT

不仅“上知天文、下知地理”,似乎还能对答如流、出口成诗,甚至还能写剧本、编音乐、写代码——最近,一款名叫ChatGPT的人工智能聊天机器人火爆全球。由此,一系列关于新一代技术变革、人工智能替代人力、巨头企业扎堆入局AI的讨论在…

Multi Paxos

basic paxos 是用于确定且只能确定一个值,“只确定一个值有什么用?这可解决不了我面临的问题,例如每个用户都要多次保存数据.” 你心中可能有这样的疑问。 原simple paxos论文里有提到一连串个instance of paxos [4] 但没有提出 multi paxos的概念&…

ChatGPT国内镜像站试用,聊天、Python代码生成。

ChatGPT国内镜像站试用,聊天、Python代码生成。 (本文获得CSDN质量评分【91】)【学习的细节是欢悦的历程】Python 官网:https://www.python.org/ Free:大咖免费“圣经”教程《 python 完全自学教程》,不仅仅是基础那么简单…… …

前端开发:关于diff算法详解

前言 前端开发中,关于JS原生的内容和前端算法相关的内容一直都是前端工作中的核心,不管是在实际的前端业务开发还是前端求职面试,都是非常重要且必备的内容。那么本篇博文来分享一个关于前端开发中必备内容:diff算法,d…

ChatGPT背后的技术可以给数据治理带来哪些神奇的效果?_光点科技

最近,由美国人工智能研究室OpenAI开发的全新“聊天机器人”ChatGPT火了。作为一款人工智能语言模型,它不仅能和人展开互动,还可以写文章、制定方案、创作诗歌,甚至编写代码、检查漏洞样样精通,上线仅两个月全球活跃用户…

Python雪花代码

前言 用python画个雪花玩玩,源码在文末公众号哈。 雪花类 class Snow(): #雪花类 def __init__(self): self.r 6 #雪花的半径 self.x ra.randint(-1000,1000) #雪花的横坐标 self.y ra.randint(-500,5…

剑指 Offer 10- I. 斐波那契数列[c语言]

目录题目思路代码结果该文章只是用于记录考研复试刷题题目 力扣斐波那契数列 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: F(0) 0, F(1) 1 …

卷积神经网络-D2L

从全连接层到卷积 企业级理解卷积 不稳定输入 稳定输出 求系统存量 - 信号系统周围像素点如何产生影响 - 图像处理一个像素点如何试探 - 图像识别 好处: 平移不变性和局部性 图像卷积 import torch from torch import nn from d2l import torch as d2ldef try_…

Java高频面试题,ReentrantLock 是如何实现锁公平和非公平性的?

我先解释一下个公平和非公平的概念。 公平,指的是竞争锁资源的线程,严格按照请求顺序来分配锁。 非公平,表示竞争锁资源的线程,允许插队来抢占锁资源。 ReentrantLock 默认采用了非公平锁的策略来实现锁的竞争逻辑。 其次&…

SqlServer的LDF文件丢失, 如何仅用MDF文件恢复数据库呢?(已解决)

笔者的一个大小为2 TB的SQL Server的database的LDF文件在玩存储盘映射的过程中莫名其妙的丢失了. 好在MDF文件还在. 笔者慌了, Bruce Ye告诉笔者, 不用着急, 光用MDF也可以把数据库弄回来的. 笔者就问Bruce, 假设我可以容忍LDF中信息的丢失的话, 那么该如何恢复这个数据库呢?我…

快速搭建个人在线书库,随时随地畅享阅读!

前边我们利用NAS部署了个人的导航页、小说站、云笔记,今天,我们再看看怎么部署一个个人的在线书库。 相信很多朋友都在自己的电脑中收藏了大量的PDF、MOBI等格式的电子书籍,但是一旦换了一台设备,要么是无法翻阅,要么…

如何为报表开发工具 FastReport .NET 设置 Apache 2 Web 服务器?

FastReport .NET是一款全功能的Windows Forms、ASP.NET和MVC报表分析解决方案,使用FastReport .NET可以创建独立于应用程序的.NET报表,同时FastReport .Net支持中文、英语等14种语言,可以让你的产品保证真正的国际性。专业版和企业版包括Fast…

FortiTalk | “三英论安全”之OT安全热门话题解读

OT安全热门话题解读 在数字化转型时代,OT/IT融合已经成为主旋律,可能很多人还没有意识到“工厂”已经不是以前的“工厂”。从封闭走向互联、从现场走向远程、从手动走向自动,这种变革带来的不仅是便捷和效率,更潜藏着巨大的网络安…

【数据结构】基础:图的最短路径问题(附C++源码)

【数据结构】基础:图的最短路径问题(附C源码) 摘要:将会在数据结构专题中开展关于图论的内容介绍,其中包括四部分,分别为图的概念与实现、图的遍历、图的最小生成树以及图的最短路径问题。本文介绍图的最短…

LeetCode 105. 从前序与中序遍历序列构造二叉树 -- 数据结构基础

从前序与中序遍历序列构造二叉树 中等 1.9K 相关企业 给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], i…

基于MATLAB的MIMO信道估计(附完整代码与分析)

目录 一. 介绍 二. MATLAB代码 三. 运行结果与分析 一. 介绍 本篇将在MATLAB的仿真环境中对比MIMO几种常见的信道估计方法的性能。 有关MIMO的介绍可看转至此篇博客: MIMO系统模型构建_唠嗑!的博客-CSDN博客 在所有无线通信中,信号通过…

05- 线性回归算法 (LinearRegression) (算法)

线性回归算法(LinearRegression)就是假定一个数据集合预测值与实际值存在一定的误差, 然后假定所有的这些误差值符合正太分布, 通过方程求这个正太分布的最小均值和方差来还原原数据集合的斜率和截距。当误差值无限接近于0时, 预测值与实际值一致, 就变成了求误差的极小值。 fr…

【Calcite源码学习】ImmutableBitSet介绍

Calcite中实现了一个ImmutableBitSet类,用于保存bit集合。在很多优化规则和物化视图相关的类中都使用了ImmutableBitSet来保存group by字段或者聚合函数参数字段对应的index,例如: //MaterializedViewAggregateRule#compensateViewPartial()…

浏览器渲染原理JavaScript V8引擎

浏览器渲染原理 前言 在我们面试过程中,面试官经常会问到这么一个问题,那就是从在浏览器地址栏中输入URL到页面显示,浏览器到底发生了什么? 浏览器内有哪些进程,这些进程都有些什么作用;浏览器地址输入U…