【C++干货基地】深度理解C++中的高效内存管理方式 new delete

2024-04-25 10:20

本文主要是介绍【C++干货基地】深度理解C++中的高效内存管理方式 new delete,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


在这里插入图片描述

🎬 鸽芷咕:个人主页

 🔥 个人专栏: 《C++干货基地》《粉丝福利》

⛺️生活的理想,就是为了理想的生活!

引入

  哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作为一门篇底层的一种语言,世面的免费课程大多都没有教明白。所以本篇专栏的内容全是干货让大家从底层了解C++,把更多的知识由抽象到简单通俗易懂。

⛳️ 推荐

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

文章目录

  • 引入
  • ⛳️ 推荐
  • 一、C/C++内存分布
    • 1.1 内存布局图:
    • 1.2 C/C++程序内存分配的几个区域:
  • 二、C语言的内存管理方法
  • 三、C/C++ 中的内存管理方法
    • 3.1 new 和 delete 的使用
    • 3.2 new 和 delete 在创建自定义类型时候的动作
  • 四、new和delete的实现原理
    • 4.1 operator new与函数
    • 4.3 使用new 和new[ ] 是如何获取大小的
    • 4.4 delete 和 delete[ ] 的区别
  • 五、 malloc/free和new/delete的区别

一、C/C++内存分布

1.1 内存布局图:

在这里插入图片描述
用通俗易懂的话来描述就是:

  1. 栈区(stack):存放的是我们平常创建的变量 形参 等 临时变量!
  2. 堆区(heap):目前我们学的动态内存分配 都是在堆区开辟的!
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段可执行代码 和 只读 常量

1.2 C/C++程序内存分配的几个区域:

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
    配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

二、C语言的内存管理方法

在C 语言中 我们通常都是使用 malloc 来申请空间,使用 free 来释放空间

void Test()
{int* p1 = (int*)malloc(sizeof(int));free(p1);// 1.malloc/calloc/realloc的区别是什么?int* p2 = (int*)calloc(4, sizeof(int));int* p3 = (int*)realloc(p2, sizeof(int) * 10);free(p3);
}

但是malloc 还有 realloc 开辟空间都有失败的风向因此再项目中如果有开辟空间的行为是我们还得专门去写一个判断语句来避免空间开辟失败的其他报错。

void Test()
{int* tmp = (int*)malloc(sizeof(int));if (tmp == NULL){perror("malloc file");exit(-1);}int* p1 = tmp;free(p1);
}

三、C/C++ 中的内存管理方法

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

  • 在使用C语言的内存管理方式是不能进行自己去创建类对象的
  • 而这也是我们设计 new 和 delete 的原因,更方便的开辟空间

3.1 new 和 delete 的使用

int main()
{// 1、用法上,变简洁了int* p0 = (int*)malloc(sizeof(int));int* p1 = new int;int* p2 = new int[10]; // new 10个int对象// 2、可以控制初始化int* p3 = new int(10); // new 1个int对象,初始化成10int* p4 = new int[10]{ 1,2,3,4,5 };return 0;
}

以上就是new 和 delete 的简单使用方法相信大家看一眼就会了非常的简单好上手。

3.2 new 和 delete 在创建自定义类型时候的动作


struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr), _val(val){}
};struct ListNode* CreateListNode(int val)
{struct ListNode* newnode = (struct ListNode*)malloc(sizeof(struct ListNode));if (newnode == NULL){perror("malloc fail");return NULL;}newnode->_next = NULL;newnode->_val = val;return newnode;
}

以上是我们在C语言中开辟链表的方式,申请空间时书写非常麻烦还要去检查一下开辟空间是否失败。

  • 而new是可以直接去给我们生成空间和自动调用构造函数初始化的

struct ListNode
{ListNode* _next;int _val;ListNode(int val):_next(nullptr), _val(val){}
};int main()
{// 1、用法上,变简洁了int* p0 = (int*)malloc(sizeof(int));int* p1 = new int;int* p2 = new int[10]; // new 10个int对象// 2、可以控制初始化int* p3 = new int(10); // new 1个int对象,初始化成10int* p4 = new int[10]{ 1,2,3,4,5 };// 3、自定义类型,开空间+构造函数// 4、new失败了以后抛异常,不需要手动检查ListNode* node1 = new ListNode(1);ListNode* node2 = new ListNode(2);ListNode* node3 = new ListNode(3);//...return 0;
}

四、new和delete的实现原理

new 关键字的好用我们已经体验过,malloc 和 new相比简直一个天上一个地下,用过new的人都不会再选择malloc 了, 那他的底层究竟是怎么实现的呢?

  • 从下面这段代码我们可以看对内置类型进行new 开辟空间是去调用 operator new 函数来进行开辟空间的。
    在这里插入图片描述

4.1 operator new与函数

  • 那么operator new 函数是怎么实现的呢?
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空               间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{// try to allocate size bytesvoid* 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: 该函数最终是通过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的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

从这里我们就可以看到 operator new 实际上用 malloc 来封装实现开辟空间的

  • 也就是说,new 的底层是 调用 operator new 而 operator new 的底层是malloc
  • 那么new的底层实际上是对 malloc 封装实现的。

delete 也是同理在 delete 中我们发现 delete 是通过调用 operator delete 来实现开辟空间的而 operator delete 是通过 _free_dbg 来释放空间,_free_dbg 是free宏的 调用函数。

  • 所以 delete 的底层就是 对 free 进行封装实现的。

4.3 使用new 和new[ ] 是如何获取大小的

这个问题就很简单了,我们编译器其实是可以自动获取类型大小的,我们使用sizeof() 关键字都可以获取大小为什么编译器不可以呢?

  • 所以我们看到了,在汇编代码中一个 push 的大小就是我们要开空间的字节

在这里插入图片描述

  • 而 new[ ] 进行开辟连续的空间时我们就要注意了

本来我申请个连续的空间难道不是40个字节嘛,为什么给我多开辟了4个字节?

在这里插入图片描述

其实多出来的4个字节是为了在调用构造函数的时候记录需要构造的次数,已经析构的时候需要析构多少次。

  • new[10]进行开辟空间时的步骤是这样的
    在这里插入图片描述

4.4 delete 和 delete[ ] 的区别

前面我们看到了 在使用 new[ ] 进行开辟数组空间的时候其实会多开4个字节记录数组个数那么这个数组个数的作用是干嘛呢?

  • 记录数组的作用是为了给我们调用析构函数来用的
  • 大家看一下下面的这段代码,使用 new[ ] 创建的空间不使用 deletet[ ] 释放空间居然不报错。
class A
{
public:A(int a = 0): _a(a){cout << "A():" << this << endl;}private:int _a;
};int main()
{A* ptr1 = new A[10];delete ptr1;return 0;
}

在这里插入图片描述

这是因为我们一旦没有写析构函数的话,new[ ] 就不会多开4个字节。那么我们就行free的时候指针就是在开头的位置不要往前偏移才能释放

  • 而我们一旦写了析构函数new[ ] 就会为我们多开 4个字节存放数量

在这里插入图片描述
在这里插入图片描述

五、 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++干货基地】深度理解C++中的高效内存管理方式 new delete的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/934411

相关文章

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

Win11安装PostgreSQL数据库的两种方式详细步骤

《Win11安装PostgreSQL数据库的两种方式详细步骤》PostgreSQL是备受业界青睐的关系型数据库,尤其是在地理空间和移动领域,:本文主要介绍Win11安装PostgreSQL数据库的... 目录一、exe文件安装 (推荐)下载安装包1. 选择操作系统2. 跳转到EDB(PostgreSQL 的

详解C++中类的大小决定因数

《详解C++中类的大小决定因数》类的大小受多个因素影响,主要包括成员变量、对齐方式、继承关系、虚函数表等,下面就来介绍一下,具有一定的参考价值,感兴趣的可以了解一下... 目录1. 非静态数据成员示例:2. 数据对齐(Padding)示例:3. 虚函数(vtable 指针)示例:4. 继承普通继承虚继承5.

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

C#使用SQLite进行大数据量高效处理的代码示例

《C#使用SQLite进行大数据量高效处理的代码示例》在软件开发中,高效处理大数据量是一个常见且具有挑战性的任务,SQLite因其零配置、嵌入式、跨平台的特性,成为许多开发者的首选数据库,本文将深入探... 目录前言准备工作数据实体核心技术批量插入:从乌龟到猎豹的蜕变分页查询:加载百万数据异步处理:拒绝界面

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.