C++ [内存管理]

news/2024/11/15 0:51:37/

       

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

       


目录

前言

正文

 计算机中内存分布

C语言的内存管理

内存申请函数

内存释放函数

C++内存管理

new操作符

delete操作符

特性总结

注意

原理探究

operator new和operator delete函数

operator new的底层实现

operator delete底层实现

free的底层实现

new和delete的实现

内置类型

自定义类型

定位new

使用方法

使用场景

最后


前言

 

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_FINALLYreturn;
}

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的定义表达式进行显示调构造函数进行初始化。


最后

 

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

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

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

🌟其他文章阅读推荐🌟

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

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

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

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


http://www.ppmy.cn/news/38222.html

相关文章

Java on VS Code 3月更新|AWT 代码补全、启动程序消息显示与 Spring Apps 数据可视化改进

作者&#xff1a;Nick Zhu - Senior Program Manager, Developer Division at Microsoft 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎来到我们的三月更新&#xff01;在此博客中&#xff0c;我们将为您带来一系列基础编码体验的改进&#xff0c;例如 AWT 项目相关的代码…

关于Stanza工具包的使用

目录 一、Stanza简要介绍 二、Stanza使用 2.1 安装方法 2.2 使用说明 2.2.1 以英文文本说明&#xff1a; 2.2.2 以中文文本说明&#xff1a; 一、Stanza简要介绍 Stanza是一个Python自然语言处理工具包&#xff0c;它是斯坦福自然语言处理工具的升级版。它提供了一系列的…

anomalib代码解析之三:训练过程

咱们吃个回头草吧 上面的图中&#xff0c;第55行&#xff0c;藏有玄机。前面我们没详细讲。就是这行&#xff0c;指定了&#xff0c;cfa算法&#xff0c;怎么训练的&#xff0c;算法实现细节都在这里。 那我们就得看get_model函数了&#xff1a; def get_model(config: DictC…

Python矩阵分解之QR分解

文章目录QR和RQ分解其他函数QR和RQ分解 记AAA为方阵&#xff0c;P,QP, QP,Q分别为正交单位阵和上三角阵&#xff0c;则形如AQRAQRAQR的分解为QR分解&#xff1b;形如ARQARQARQ的分解为RQ分解。 在scipy.linalg中&#xff0c;为二者提供了相同的参数&#xff0c;除了待分解矩阵…

SPSS27破解安装后,出现应用程序无法正常启动(0xc000007b)

破解完SPSS 27软件后&#xff0c;点击图标出现下图错误 可以尝试以下方法&#xff1a; 1. 在安装目录下找到VC开头的文件夹 2. 点击此软件进行修复 若修复完成&#xff0c;重新启动SPSS软件即可。 3. 若提示错误&#xff0c;显示如下界面&#xff0c;进行下面的方法 4. 下…

用户行为分析zhi应用分析模型

&#xff08;1&#xff09;基于AARRR漏斗模型分析用户行为 本文通过常用的电商数据分析指标&#xff0c;采用AARRR漏斗模型拆解用户进入APP后的每一步行为。AARRR模型是根据用户使用产品全流程的不同阶段进行划分的&#xff0c;针对每一环节的用户流失情况分析出不同环节的优化…

斐波拉契数列,有人买了一对小兔子,已知小兔子一个月后长成大兔子,大兔子每个月生一对小兔子,问:两年(24个月)之后,他一共有几对兔子。

[01]斐波拉契数列&#xff0c;有人买了一对小兔子&#xff0c;已知小兔子一个月后长成大兔子&#xff0c;大兔子每个月生一对小兔子&#xff0c;问:两年(24个月)之后&#xff0c;他一共有几对兔子。 第i月份大兔子小兔子总兔子1011210131124213532565387851381382192113341034…

7_linux进程管理

7_linux进程管理 文章目录7_linux进程管理一、进程定义二、进程查看命令三、进程优先级四、进程前后台调用五、进程信息号六、systemd守护进程七、系统中的登陆审计一、进程定义 进程的定义 程序是静态的代码文件进程是指程序运行时的形态进程是程序的一个副本进程是有生命周期…