深入篇【C++】CC++内存管理:new/delete底层原理剖析+思维导图总结

news2024/12/23 17:47:41

深入篇【C++】C&C++内存管理:new/delete底层原理剖析+思维导图总结

  • Ⅰ.C/C++内存分布
  • Ⅱ.C的内存管理
  • Ⅲ.C++的内存管理
    • ①.new/delete操作内置类型
      • 总结:
    • ②.new/delete操作自定义类型
      • 总结:
    • ③.operator new与operator delete
      • 总结:
    • ④.new/delete底层实现原理
      • 总结:
    • ⑤.定位new表达式(placement-new)
      • 总结:

在这里插入图片描述

Ⅰ.C/C++内存分布

程序中需要存储不同的数据,这些数据大致分为以下几种:局部数据,静态数据和全局数据,常量数据,动态申请的数据。
那这些数据都存在在哪里呢?
在这里插入图片描述
我们来分析一下下面这段代码的相关问题。

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
 static int staticVar = 1;
 int localVar = 1;
 int num1[10] = { 1, 2, 3, 4 };
 char char2[] = "abcd";
 const char* pChar3 = "abcd";
 int* ptr1 = (int*)malloc(sizeof(int) * 4);
 int* ptr2 = (int*)calloc(4, sizeof(int));
 int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
 free(ptr1);
 free(ptr3);
}
选择题:
   选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
   globalVar在哪里?__C__   staticGlobalVar在哪里?__C__
   staticVar在哪里?_C___   localVar在哪里?_A___
   num1 在哪里?__A__
   
   char2在哪里?_A___   *char2在哪里?__A_
   pChar3在哪里?__A__      *pChar3在哪里?_D___
   ptr1在哪里?_A___        *ptr1在哪里?_B___

在这里插入图片描述

总结
1.栈又叫做堆栈–是存储非静态局部变量(如函数参数,函数返回值,局部变量等),栈是可以向下增长的。
2.堆用于存储动态申请的数据,堆是可以向上增长的。
3.静态区/数据段是存储全局变量和静态变量的。
4.代码段是存储可执行的代码的只读常量。

Ⅱ.C的内存管理

C语言中用于动态内存管理方式有:malloc / calloc /realloc /free

int main()
{
	int* a1 = (int*)malloc(sizeof(int));
	free(a1);

	int* a2 = (int*)calloc(4,sizeof(int));

	int* a3 = (int*)realloc(a2, sizeof(int) * 5);
	//a2还需要释放嘛?
}

malloc / calloc /realloc它们的作用分别是什么呢?
maollc用来动态开辟一块大小为sizeof(类型)的空间,但不初始化。
calloc用来动态开辟一个n*sizeof(类型)的空间,并全初始化为0。
realloc用来扩容,扩容的方式有两种:
一种是原地扩容,当原来空间的后面有足够的空间可以申请那就在原来空间的基础上往后扩容。
第二种是异地扩容,当原来空间后面没有足够的空间可以申请,那就在另一块空间申请,会将原来的空间释放。
所以上面a2是不用释放的,因为当原地扩容,a2的空间就是a3正在使用的不能释放,当异地扩容是系统已经帮你释放了。

Ⅲ.C++的内存管理

C语言中的内存管理方式在C++中仍可以继续使用,但有些地方就无能为力了,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

①.new/delete操作内置类型

int main()
{
    //动态申请一个int类型的空间
	int* p3 = (int*)malloc(sizeof(int));//C
	free(p3);
	//动态申请一个10大小的int的类型的数组
	A* p1 = (A*)malloc(sizeof(A) * 10);//C
	free(p1);
	
	//动态申请一个int类型的空间
	int* p4 = new int;//C++
	delete p4;
	
	//动态申请一个int类型的空间,并初始化。
	int* p5 = new int(1);//C++
	delete p5;

	//动态申请一个10大小的int的类型的数组
	A* p2 = new A[10];//C++
	delete[] p2;
}

在这里插入图片描述
C++中内存管理的方式是:new与delete,注意它们是操作符不是函数。
new后面加类型,就是动态申请一个这个类型的大小的空间。
new后面加类型加圆括号,就是动态申请一个这个类型大小的空间并初始化。
new后面加类型加方括号,就是动态申请n个该类型的大小的空间。
而要释放资源就直接在delete后面加上该类型即可,不过当释放的资源是连续的时,就需要使用delete[ ]来释放。

从上面的使用来看,new/malloc除了用法上不同,其他方面没有上面区别的。但唯一要注意的就是new可以给变量进行初始化,而malloc不可以。
在这里插入图片描述
相比较C/C++对于内置类型动态内存的管理,我们发现C++对于内置类型使用上更方便。

总结:

1.申请和释放单个空间,用new和delete。
2.申请和释放连续的空间,用new[ ]和delete[ ]。
3.要注意匹配,不能乱搞。

②.new/delete操作自定义类型

对于自定义类型,C/C++是如何进行内存管理的呢?

class A
{
public:
	explicit A(int a = 0)
		:_a(a)
	{

	}
	A(const A& a1)
	{
		_a = a1._a;
	}
private:
	int _a;
};
int main()
{
	//动态开辟一个A类型的空间
	A* aa1 = (A*)malloc(sizeof(A));//C
	A* aa2 = new A(1);//C++
	free(aa1);
	delete aa2;

	//动态开辟一个10个A类型大小的数组
	A* aa3 = (A*)malloc(sizeof(A) * 10);//C
	A* aa4 = new A[10];//C++
	free(aa3);
	delete[] aa4;

}

对于自定义类型跟内置类型的处理方式是一致的。
但new/delete和malloc和free的最大区别在于new/detele对于【自定义类型】除了开空间,还会自动调用构造函数和析构函数。
在这里插入图片描述
在这里插入图片描述

所以如果动态开辟10个A类型大小的数组,就相当于创建了10个对象,会调用10次构造函数,这里的构造函数是默认构造函数,当没有默认构造函数时,就必须自己初始了。
我们可以这样初始化:

	A* aa4 = new A[10]{0123456789};
	//当构造函数参数为两个时,可以这样初始化
	B* bb1=new B[4]{B(1,2),B(2,3),B(4,5),B(6,7)};
	B* bb1=new B[4]{B(1,2),B(2,3),B(4,5)};
	但注意这样是不行的,创建了4个对象,你手动初始化了3个,还有一个说:你看不起谁呢!
	这种必须让构造函数变成默认构造函数(全缺省形式)的才可以。
struct ListNode
{
	int _val;
	struct ListNode* _next;

	ListNode(int x=0)
		:_val(x)
		,_next(NULL)
	{}
};
struct ListNode* BuyListNode(int x)
{
	//C  malloc单纯的开空间,还需要自己转化类型
	struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));
	//还需要检查
	if (newnode == NULL)
	{
		perror("malloc");
	}
	//需要开完空间自己手动初始化
	newnode->_next = NULL;
	newnode->_val = x;
	return newnode;
}
int main()
{
	struct ListNode* n1 = BuyListNode(1);
	struct ListNode* n2 = BuyListNode(2);

	//但C++的new就不一样了,new是开完空间后还可以自己初始化
	//怎么初始化的呢?会调用构造函数初始化
	struct ListNode* n3 = new ListNode(6);
	struct ListNode* n4 = new ListNode;
}

在这里插入图片描述

总结:

动态申请自定义类型的数据new和malloc除了使用方式上有点区别,还有一个重大区别:new在申请空间时会调用构造函数初始化,malloc在申请空间时不会去调用构造函数,不能初始化。

③.operator new与operator delete

new和delete是C++进行动态内存申请的操作符,operator new 和operator delete是系统提供的全局函数,new在底层会去调用operator new来申请空间,delete在底层会去调用operator delete来释放空间。
在这里插入图片描述

malloc就是用来申请空间的,那是不是new在使用时就调用了malloc来申请空间呢?
其实不是这样,new在申请空间时,本质上调用的是operator new这个全局函数来申请空间,而operator new 这个全局函数里面才是真正的去调用malloc申请空间的。

//operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
//尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
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);
}

可能会有点迷惑,为什么new不直接去调用malloc呢?还非要去调用operator new,operator new 里面才去使用malloc。真是莫名其妙哈。但其实有设计这样的理由的。
面向对象语言在处理失败时不喜欢用返回值,而更建议用抛异常。
而我们知道malloc在申请空间失败时返回的是空,C++不想使用这个,从而设计出一个operator new来,因为在operator new函数里,使用了malloc并且如果当申请空间失败时就会抛出异常,而不是返回空。
所以new在空间时,不能直接调用malloc,而是通过operator new函数来调用malloc。

同理delete 会先调用析构函数,再去调用operator delete释放空间。
而operator delete函数里又使用了free。

/*
operator delete: 该函数最终是通过free来释放空间的
*/
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)
我们观察free在operator中是被使用的。


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 );
         //调用free函数
     __FINALLY
         _munlock(_HEAP_LOCK);  /* release other threads */
     __END_TRY_FINALLY
     return;
}

总结:

通过上面两个全局函数我们可以知道,operator new其实也是通过malloc来申请空间的,如果申请成功就直接返回,否则就执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常,同理operator delete最终也是通过free来实现的。从这里我们可以知道operator new 函数和operator delete函数 是为了new 和delde操作符准备的。

④.new/delete底层实现原理

对于内置类型:
new和malloc ,delete和free基本类似,不同的地方是new/delete是申请单个空间。
new[ ]/delete[ ]申请的是连续的空间,new在申请空间失败时会抛异常,malloc在申请空间失败时会返回NULL。

对于自定义类型

  • <new的原理>
    1.调用operator new函数申请空间。
    2.在申请的空间上调用构造函数初始化。

  • <delete的原理>
    1.在空间上执行析构函数,完成对象中资源的清理工作。
    2.调用operator deldete函数释放对象的空间。

  • <new [ N ]的原理>
    1.调用operator new[ ]函数,operator new[ ]函数本质还是去调用operator new函数申请N个对象的空间。
    2.在申请的空间上执行N次构造函数。

  • <delete [ N ]的原理>
    1.在释放的对象空间执行N次析构函数,完成N个对象的资源清理工作。
    2.调用operator delete[ ]函数,operator delete[ ]函数本质还是去调用operator delete函数释放对象空间。

总结:

malloc/free和new/delete的区别是什么?
相同点:都是用来动态申请空间的,都是在堆上开辟,都需要用户手动释放空间。
不同点:
1.对于自定义类型在使用时,malloc只是申请空间,不会去调用构造函数,free也只是释放对象空间,而new除了申请空间外还会调用构造函数。delete在释放空间之前会先调用析构函数。

2.new/delete 是操作符不是函数,malloc/free是函数。

3.new/delete申请/释放失败会抛异常,malloc/free申请/释放失败是会返回NULL

4.malloc申请的空间不能初始化,new申请空间可以初始化。

5.malloc在申请空间时需要手动计算空间大小并传递,new在申请空间时只需加上类型即可,如果申请是多个对象,那只需在[ ]里写上对象个数即可。

6.malloc的返回值是void*,在使用时必须进行强转,而new不需要,因为new后面就跟着使用的类型。

⑤.定位new表达式(placement-new)

定位new表达式是在已经分配的原始内存空间中调用构造函数去初始化一个对象

我们知道,new底层实现的原理是先申请空间,再调用构造函数,这个构造函数是自动调用的,而这里的定位new需要我们显式的调用构造函数。
【使用格式】:
new(place_address)type或者new(place_address)type(initializer-list).
这里place_address表示已经分配的原始内存空间的地址,必须是指针。
initializer-list是初始化列表。
【使用场景】:内存池
当需要频繁的申请和释放内存时,就是每次都要在堆上申请空间,会觉得很烦,所以有人就搞出一个内存池的概念,从堆上提前要些空间放进内存池中,当以后要用申请空间时,就可以直接从内存池里获取。不需要再到堆上申请,但内存池上的空间是没有初始化的。
所以定位new表达式在时间中一般是配合内存池使用,因为内存池分配的内存没有初始化,所以如果是自定义类型的对象,需要使用new定义表达式来显式调用构造函数来初始化对象。
在这里插入图片描述

class A
{
public:
	explicit A(int a = 0)
		:_a(a)
	{

	}
	A(const A& a1)
	{
		_a = a1._a;
	}
	~A()
	{
		cout << "~A()" << endl;
   }
private:
	int _a;
};

int main()
{
	//aa1可不是对象,aa1现在只不过是指向与A对象一样大的空间,不能算作对象,因为构造函数没有执行,没有实例化。
	A* aa1 = (A*)malloc(sizeof(A));
	
	new(aa1)A;//显式的调用构造函数,要注意如果A的构造函数有参数需要传参。

	aa1->~A();//手动的调用析构函数

	free(aa1);//手动的调用free

	//同理如下
	A* aa2 = (A*)operator new(sizeof(A));
	new(aa2)A(10);
	aa2->~A();
	free(aa2);
}

在这里插入图片描述

总结:

1.定位new表达式是具有价值的,在内存池应用方面很有用,因为从内存池分配的内存没有初始化,需要我们显式的去调用构造函数来初始化对象。
2.池化技术是用来提高效率的。
3.定位new就是在模拟new。

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

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

相关文章

【C++String类使用】万字详解保姆级教学,手把手教你使用string类。

string类的使用 什么是string类&#xff1f;string构造string();string (const char* s);string (const string& str);string (const string& str, size_t pos, size_t len npos);string (const char* s, size_t n);string (size_t n, char c);template < class In…

《The Element of Style》阅读笔记 —— 章节 III A Few Matters of Form

前言&#xff1a;本篇为书籍《The Element of Style》第三章的阅读笔记。 本书电子版链接&#xff1a;http://www.jlakes.org/ch/web/The-elements-of-style.pdf 章节 I Elementary Rules of Usage 阅读笔记&#xff1a;链接章节 II Elementary Principles of Composition 阅读…

JavaScript进阶之路(一)初学者的开始

一&#xff1a;写在前面的问题和话 一个javascript初学者的进阶之路&#xff01; 背景&#xff1a;3年后端&#xff08;ASP.NET&#xff09;工作经验&#xff0c;javascript水平一般般&#xff0c;前端水平一般般。学习资料&#xff1a;犀牛书。 如有误导&#xff0c;或者错…

Git日常使用技巧 - 笔记

Git日常使用技巧 - 笔记 Git是目前世界上最先进的分布式版本控制系统 学习资料 廖雪峰 学习视频 https://www.bilibili.com/video/BV1pX4y1S7Dq/?spm_id_from333.337.search-card.all.click&vd_source2ac127043ccd79c92d5b966fd4a54cd7 Git 命令在线练习工具 https://l…

多线程 -- Thread类的基本用法

本篇重点 什么是变量捕获?? 有关线程的操作 线程创建线程中断线程等待线程休眠获取线程实例目录 1. 线程创建2. 线程中断变量捕获 线程的六种状态NEW 状态TERMNATED 状态RUNNABLE 就绪状态TIMED_WAITING 状态 1. 线程创建 关于线程的创建看上篇博客, 里面为线程的创建提供…

数据库事务到底是什么?

目录 场景&#xff08;两个用户之间进行转账操作&#xff09;&#xff1a; 需要的操作步骤&#xff1a; 事务 事务的四大特性&#xff1a; 一、原子性 &#xff08;1&#xff09;什么是回滚操作 &#xff08;2&#xff09;数据库恢复操作&#xff0c;如何知道数据恢复如初…

C++ Stack&queue&deque

C Stack&#xff06;queue&#xff06;deque &#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;C &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容主要讲解了栈和…

如何在华为OD机试中获得满分?Java实现【字符串通配符】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1、题目描述2、输入描述3、输出描述…

Rust 笔记:Rust 语言中应用正则表达式

Rust 笔记 Rust 语言中应用正则表达式 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/…

【Linux】线程详解之线程互斥与同步

文章目录 Linux线程互斥一、进程线程间的互斥相关概念1.临界资源和临界区2.互斥和原子性 二、互斥量mutex1.抢票程序是否引入互斥量现象观察2.抢票程序原理分析3.互斥量的接口4. 加锁后的程序5.互斥量原理探究 可重入VS线程安全一、概念1.线程安全2.重入 二、常见的线程不安全的…

【P37】JMeter 仅一次控制器(Once Only Controller)

文章目录 一、仅一次控制器&#xff08;Once Only Controller&#xff09;参数说明二、测试计划设计2.1、测试计划一2.1、测试计划二 一、仅一次控制器&#xff08;Once Only Controller&#xff09;参数说明 可以让控制器内部的逻辑只执行一次&#xff1b;单次的范围是针对某…

Spring Authorization Server 系列(一)环境搭建

Spring Authorization Server 中的scope参数解析 前提依赖版本问题确定Spring Boot 版本确定 Spring Authorization Server 版本最终的依赖 第一个 Spring Authorization Server Demo 前提 由于 Spring 最新的 OAuth2 解决方案 已经由 Spring Security 下的 OAuth2 模块独立出…

从零开始学习JavaScript:轻松掌握编程语言的核心技能①

从零开始学习JavaScript&#xff1a;轻松掌握编程语言的核心技能 一&#xff0c;JavaScript 简介为什么学习 JavaScript?JavaScript 用法 二&#xff0c;JavaScript 输出JavaScript 显示数据JavaScript&#xff1a;直接写入 HTML 输出流 三&#xff0c;JavaScript 语法JavaScr…

VS2022发布独立部署的.net程序

.net core支持依赖框架部署和独立部署两种方式&#xff0c;之前学习时是在VSCode中使用dotnet命令发布的。但是在VS2022中却不知道该如何设置。以获取PDF文件使用字体的项目为例&#xff0c;VS2022中默认编译的是依赖框架部署方式&#xff08;编译的结果如下图所示&#xff09;…

Android进阶 View事件体系(三):典型的滑动冲突情况和解决策略

Android进阶 View事件体系&#xff08;三&#xff09;&#xff1a;典型的滑动冲突情况和解决策略 内容概要 本篇文章为总结View事件体系的第三篇文章&#xff0c;前两篇文章的在这里&#xff1a; Android进阶 View事件体系&#xff08;一&#xff09;&#xff1a;概要介绍和实…

Oracle——数据操纵DML(一)

STU1 1、不指定字段的整行插入 在STU1中新增一名同学的基本信息&#xff0c;SQL如下&#xff1a; INSERT INTO test.stu1 VALUES(0001,牛牛,男,24,to_date(1988-05-25,YYYY-MM-DD),12外语)格式如下&#xff1a; INSERT INTO 表名 VALUES(值1,值2,...,值n)对于CHAR或VARCHAR等…

sql-labs SQL注入平台——第二关Less-2 GET - Error based - Intiger based (基于错误的GET整型注入)

Less-2 GET - Error based - Intiger based (基于错误的GET整型注入) 一、先确认漏洞是否存在 &#xff08;1&#xff09;查询 id1返回查询结果正常 &#xff08;2&#xff09;查询 id1’返回查询结果报错&#xff0c;可能存在SQL注入 &#xff08;3&#xff09;查询 id1 …

路径规划算法:基于帝国主义竞争优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于帝国主义竞争优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于帝国主义竞争优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用…

Dubbo环境搭建

1.搭建zookeeper注册中心环境 zookeeper下载地址 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ptMw7rb-1685261782669)(b894c0cbb6501ca97145d3b09685ae8f.png)] 在bin文件下&#xff0c;启动zkServer.cmd会有报错&#xff0c;处理需要在condi…

你所不知道的 数据在内存中储存 的来龙去脉

那么好了好了&#xff0c;宝子们&#xff0c;今天给大家介绍一下 “数据在内存中储存” 的来龙去脉&#xff0c;来吧&#xff0c;开始整活&#xff01;⛳️ 一、数据类型的介绍 &#xff08;1&#xff09;整型和浮点型&#xff1a; &#xff08;2&#xff09;其他类型…