C++ [内存管理]

       

本文已收录至《C++语言》专栏!
作者:ARMCSKGT

       


目录

前言

正文

计算机中内存分布

C语言的内存管理

内存申请函数

内存释放函数

C++内存管理

new操作符

delete操作符

特性总结

注意

原理探究

operator new和operator delete函数

operator new的底层实现

operator delete底层实现

free的底层实现

new和delete的实现

内置类型

自定义类型

定位new

使用方法

使用场景

malloc/free与new/delete的区别

最后


前言

C++的内存管理与C语言在底层原理上相似,但是由于C++是面向对象的语言,在面向对象的思想上,需要对C语言的内存管理函数进行封装以适合面向对象的一些特性,所以本节将对C++的内存管理知识进行介绍!


正文

计算机中内存的分布并非是所有的进程都在内存的大环境下一起工作,而是将内存分为几个区域互不干扰,就像公司中各区域的模块划分,各司其职!


计算机中内存分布


在C/C++程序的内存分布中,有栈区,静态区,堆区等...,这些区域各司其职,互不干扰! 


说明

  1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段--存储全局数据和静态数据。
  5. 代码段--可执行的代码/只读常量。
  6. 内核空间是操作系统的空间,我们无法访问和操作!

C语言的内存管理


内存申请函数

  

malloc函数:申请指定字节大小的空间

//使用
int *i = (int*)malloc(sizeof(int)*n); //申请n块int类型的空间
char *c = (char*)malloc(sizeof(char)*m); //申请m块char类型的空间

malloc函数申请空间时不会将空间初始化,所以使用时最好手动初始化!

  

  

calloc函数:申请指定字节大小的空间并将申请的空间初始化为0

//使用
double *d = (double*)malloc(n,sizeof(double)); //申请n块double类型的空间并初始化为0
char *c = (char*)malloc(m,sizeof(char)); //申请m块char类型的空间并初始化为0

calloc与malloc的区别在于函数的参数列表不同且会主动初始化空间!

  

  

realloc函数:对已申请的空间进行扩容,若空间不存在则功能与malloc相同

//使用
int *i=(int*)malloc(szieof(int)*n); //先申请n块int空间
int *tmp = (int*)realloc(i,sizeof(int)*(m+n)); //在原来n块的基础上扩大m块
i = tmp;

注意:使用内存申请函数需要对申请的空间进行检查,防止空指针和野指针的访问,且realloc申请空间如果直接使用原指针接收,一旦申请识别会造成数据丢失和内存泄漏!


内存释放函数

有内存的申请就有内存的释放,所谓有借有还再借不难!

  

free:释放指针指向的内存空间

//使用
int *i = (int*)malloc(sizeof(int)*n); 
char *c = (char*)malloc(m,sizeof(char)); 
free(i); //使用完后及时释放内存
free(c);
i = c = NULL: //将指针置空

在C语言中,只有动态开辟的内存(堆区内存)才能使用free释放且同一块空间不能释放,释放完成后要将指针置空。


这些函数在C++语言中也可以使用,但是C语言的这些内存管理函数是针对内置类型进行设计的,对于面向对象来说,无能为力!


C++内存管理


C++在C的内存管理基础上并未引入新的函数,而是对C语言的内存管理函数进行封装,形成新的关键字newdelete


new操作符

  

使用方式:

char *c = new char[10]; //申请10个char类型的空间
double *d = new(1.23); //申请一个double类型的空间,并初始化为1.23
int *i = new int[5]{1,2,3,4,5}; //申请5个int类型的空间并初始化为1,2,3,4,5

说明

  • new申请内置类型的空间不会自动初始化,类似于malloc,但是对于自定义类型会调用其构造函数
  • new申请的空间指针不需要强制类型转换,且不需要进行检查,因为申请失败new将抛异常(C++捕获出错的机制)!

delete操作符

delete的使用分为两种:delete 指针&delete[] 指针

//对于以下两种内存的申请,其释放方式是不一样的

int *i = new int(5); //申请一块int空间
int *n = new int[10]; //申请10块int类型的空间

delete i;
delete[] n;

说明

  • delete对于内置类型和自定义类型都可以使用
  • 释放空间时与free不同的是,如果是自定义类型会调用对于的析构函数进行内存释放

与C语言中的free不同,C语言中的free可以释放所有动态申请的空间,而C++则需要成套使用!

  

成对搭配规则:new搭配delete,new[] 搭配delete[]

  

所以上述示例代码中,new int(5) 使用 delete 释放,new int[10] 使用 delete[] 释放!


特性总结

  • new/new[] 对于自定义类型会调用其构造函数,内置类型不做初始化处理
  • delete/delete[] 对于自定义类型会调用其析构函数
new和delete使用图示

注意

  

new/delete和new[]/delete[]要严格成对使用,不能混用。包括C语言的malloc/calloc/realloc/free也不能与C++中的new和delete混用,否则会发生各种各样的问题!

严格遵守:

  • new申请 - delete释放
  • new[]申请 - delete[]释放
  • malloc/calloc/realloc申请 - free释放 

这些搭配原则一定要严格遵守!


在C++中我们推荐优先使用new和delete系列,必要时搭配C语言内存管理函数使用!


原理探究


new和delete能够自动调用构造函数和析构函数的功能得益于封装


operator new和operator delete函数

new和delete并不是函数,而是内存申请和释放的操作符,当我们使用时会在底层调用operator new和operator delete全局函数!

    

调用关系:

  • new 调用 operator new
  • delete 调用 operator delete
  • new[] 调用 operator new[]
  • delete[] 调用 operator delete[]
  • operator new[] 最终调用 operator new
  • operator delete[] 最终调用 operator delete

从这里可以发现,operator new和operator delete是我们研究的主要对象


operator new的底层实现

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
	// 尝试进行空间申请
	void* 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底层实现

//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的底层实现

//free的实现,底层调用_free_dbg
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

通过上述实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。


new和delete的实现


内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似。

不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL


自定义类型

new的原理1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对象的构造
delete的原理1. 在空间上执行析构函数,完成对象中资源的清理工作
2. 调用operator delete函数释放对象的空间
new[](new T[N])的原理

1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请

 
2. 在申请的空间上执行N次构造函数

delete[]的原理

1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理

 
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间


定位new


使用方法

   

定位new的功能是对一块已有的空间初始化! 

例如对于一个自定义类型,如果我们使用malloc申请空间后,想要使用构造函数初始化这个对象就需要使用定位new!

  

使用方法:

new(对象指针) 构造函数();
//示例
class A
{
public:
    A() {}
    A(int n) {}
};

int main()
{
    A* a = (A*)malloc(sizeof(A));

    new(a) A(); //调用无参构造函数
    //new(a) A(10); //调用带参构造函数

    a->~A(); //手动调用析构函数释放对象所使用的空间
    operator delete (a); //释放对象所占空间(我们手动调用了析构只需要释放对象空间就行了)
    return 0;
}

注意: 一个对象只能调用一次构造函数,定位new多次调用构造函数编译器也不会报错且也会成功初始化当前这个对象;虽然编译器无法察觉我们使用定位new多次调用构造函,但如果使用定位new,最好遵守规则!


使用场景

我们频繁的在堆上申请和释放空间效率非常低,因为语言会在底层帮我们调用系统接口处理,如果我们一次性申请一大块空间然后自己管理分配使用,这样调用的层次从语言->操作系统提升为只有语言的层次,这样程序的效率就会提高!而这种开辟空间我们自己管理的技术是一种内存池技术!

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。


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在释放空间前会调用析构函数完成空间中资源的清理

最后

 <C++ 内存管理> 的知识到这里就介绍的差不多了,本节我们介绍了new和delete的使用,这里非常重要的一点是内存管理操作符一定要配对使用,不能混用,相信学习了这些C++的内存管理后,以后进行内存申请和管理就更加方便了!

本次 <C++ 内存管理> 就介绍到这里啦,希望能够尽可能帮助到大家。

如果文章中有瑕疵,还请各位大佬细心点评和留言,我将立即修补错误,谢谢!

🌟其他文章阅读推荐🌟

C++ <类和对象 - 下> -CSDN博客

 C++ <类和对象 - 中> -CSDN博客 

C++ <类和对象 - 上> -CSDN博客

🌹欢迎读者多多浏览多多支持!🌹

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】STL之string的使用和模拟实现

初阶的C语法和基本的类和对象我们已经学过了&#xff0c;下面我们会步入一段新的旅程。本章我们将初步了解STL(标准模板库)&#xff0c;并且深入探讨其中一个非常重要的容器———string。 目录 &#xff08;一&#xff09;STL简介&#xff08;了解即可&#xff09; &#xf…

Hashtable、HashMap、ConcurrentHashMap的区别

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步。 Hashtable和HashMap、ConcurrentHashMap 之间的区别? HashMap:线程不安全&#xff0c;key允许为null Hashtable:线程安全&#xff0c;使用synchronized锁Hashta…

2.4 特征工程

2.4 特征工程 李沐 B站:https://space.bilibili.com/1567748478/channel/collectiondetail?sid=28144 课程主页:https://c.d2l.ai/stanford-cs329p/ 1. 为什么需要特征工程: 特征工程 数据集进行特征提取,以使机器学习模型在对经过特征工程处理过的数据进行学习时可以更快…

(02)基础强化:面向对象,变量作用域,封装,继承,虚方法,可访问性

一、面向对象概念复习 1、什么是面向对象&#xff1f;OOP&#xff08;Object-Oriented Programming&#xff09; 一种分析问题的方式&#xff0c;增强了程序的可扩展性。 OOP面向对象编程 OOA面向对象分析 OOAD面向对象分析与设计&#xff08;…

Redis管道(pipeline)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1、管道(pipeline)的基本概念2、管道实操3、小总结前言 在正式讲解Redis管道之前&#xff0c;先引入一个面试题&#xff1a; 如何优化频繁命令往返造成的性能瓶…

【Hello Linux】线程控制

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍linux中的线程控制 线程控制线程创建线程等待线程终止线程分离线程id和进程地址空间布局线程创建 我们可以通过下面pthread_c…

蓝桥杯基础14:BASIC-1试题 闰年判断

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年&#xff1a; 1. 年份…

Java面向对象 - 封装、继承和多态的综合练习(答案+知识点总结)第1关:封装、继承和多态进阶(一)+ 第2关:封装、继承和多态进阶(二)

目录 第1关&#xff1a;封装、继承和多态进阶&#xff08;一&#xff09; 报错总结 & 注意事项&#xff1a; 第2关&#xff1a;封装、继承和多态进阶&#xff08;二&#xff09; 源码&#xff1a; 报错总结 & 注意事项&#xff1a; 思维导图免费制作网站&#xf…

软考软件设计师下午试题一

数据流图基本图形元素 外部实体 外部系统是当前系统之外的系统 数据存储 在里面存储数据后还能取出来用 跟实体没有关系&#xff0c;他负责存储加工的数据或者提供数据给加工 加工 灰洞的解释比如输入需要两个才能得到输出的&#xff0c;但是他只输入了一个就是灰洞 数…

Matlab傅里叶级数展开(附结果图)

Matlab傅里叶级数展开&#xff08;附结果图&#xff09; 代码下载链接 代码下载链接 代码下载链接 如下图所示&#xff1a;

“唯一靶点”的华堂宁会成控糖爆品吗?

一上市&#xff0c;两次“断货”的货华堂宁有爆品那味儿了。 2022年10月28日华领医药-B&#xff08;02552.HK&#xff09;公告华堂宁&#xff08;多格列艾汀&#xff09;正式进入商业化&#xff0c;一周后各个渠道便进入到了断货和限售的状态。 对于一个不在传统九大降糖药品…

元宇宙与网络安全

元宇宙是一种虚拟现实空间&#xff0c;用户可以在计算机生成的环境中进行互动。元宇宙的应用范围很广&#xff0c;比如房地产&#xff0c;医疗&#xff0c;教育&#xff0c;军事&#xff0c;游戏等等。它提供了更具沉浸感的体验&#xff0c;更好地现实生活整合&#xff0c;以及…

组件、套件、 中间件、插件

组件、套件、 中间件、插件 组件 位于框架最底层&#xff0c;是由重复的代码提取出来合并而成。组件的本质&#xff0c;是一件产品&#xff0c;独立性很强&#xff0c;组件的核心&#xff0c;是复用&#xff0c;与其它功能又有强依赖关系。 模块 在中台产品和非中台产品中&…

C语言程序环境和预处理

文章目录程序的翻译环境和执行环境详解编译和链接翻译环境编译本身也分为几个阶段预处理编译汇编链接段表符号表的合并预处理详解预定义符号#define#define 定义标识符#define定义宏#define替换规则#和#### 的作用带副作用的宏参数宏和参数的对比宏和函数的一个对比命名约定#un…

FastestDet:比yolov-fastest更快!更强!全新设计的超实时Anchor-free目标检测算法

本篇文章转自于知乎——qiuqiuqiu,主要设计了一个新颖的轻量级网络! 代码地址:https://github.com/dog-qiuqiu/FastestDet 1 概述 FastestDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法如yolov5n, yolox-nano, nanoDet, pp-yolo-tiny, Fast…

CSS基础知识,必须掌握!!!

CSS基础知识Background&#xff08;背景&#xff09;CSS文本格式文本颜色文本对齐格式文本修饰文本缩进CSS中的字体字体样式字体大小CSS链接&#xff08;link&#xff09;CSS列表不同列表标项CSS列表项用图片作为标记CSS列表标记项位置CSS中表格&#xff08;table&#xff09;表…

Shell脚本之嵌套循环与中断跳出

1、双重循环 1.1 格式 #!/bin/bash for ((i9;i>1;i--)) do for ((j9;j>$i;j--)) do echo -n -e "$j$i$[$i*$j]\t" done echo done1.2 实例操作 2.1 格式 #!/bin/bash for ((a1;a<9;a)) dofor ((b9;b>a;b--))doecho -n " "donefor((c1;c<…

系统信息:uname,sysinfo,gethostname,sysconf

且欲近寻彭泽宰&#xff0c;陶然共醉菊花怀。 文章目录系统信息系统标识 unamesysinfo 函数gethostname 函数sysconf()函数系统信息 系统标识 uname 系统调用 uname()用于获取有关当前操作系统内核的名称和信息&#xff0c;函数原型如下所示&#xff08;可通过"man 2 un…

面向对象编程(基础)7:再谈方法(重载)

目录 7.1 方法的重载&#xff08;overload&#xff09; 7.1.1 概念及特点 7.1.2 示例 举例1&#xff1a; 举例2&#xff1a; 举例3&#xff1a;方法的重载和返回值类型无关 7.1.3 练习 **练习1&#xff1a;** 练习2&#xff1a;编写程序&#xff0c;定义三个重载方法并…

如何大批量扫描的发票进行ocr识别导出Excel表格和WPS表格

OCR技术&#xff1a;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是将数字图像中的文字识别成字符代码的技术&#xff0c;在发票识别中应用广泛。通过OCR技术&#xff0c;可以将图片发票上的信息识别出来&#xff0c;并导出到Excel表格中…
最新文章