优化C++资源利用:探索高效内存管理技巧

news2024/11/26 22:31:16

W...Y的主页 😊

代码仓库分享💕 


🍔前言:
我们之前在C语言中学习过动态内存开辟,使用malloc、calloc与realloc进行开辟,使用free进行堆上内存的释放。进入C++后对于动态内存开辟我们又有了新的内容new与delete。今天我们来学习C++中的动态内存开辟!

我们先来进行一下内存管理的复习。

目录

C/C++内存分布

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

C++内存管理方式

new/delete操作内置类型

new和delete操作自定义类型

operator new与operator delete函数

new和delete的实现原理

内置类型

 自定义类型

定位new表达式(placement-new)

C++与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);
}
1. 选择题:
 选项: A.栈  B.堆  C.数据段(静态区)  D.代码段(常量区)
 globalVar在哪里?____  staticGlobalVar在哪里?____
 staticVar在哪里?____  localVar在哪里?____
 num1 在哪里?____
 char2在哪里?____  *char2在哪里?___
 pChar3在哪里?____    *pChar3在哪里?____
 ptr1在哪里?____     *ptr1在哪里?____

 globalVar在哪里?C  staticGlobalVar在哪里?C  staticVar在哪里?C  localVar在哪里?A  num1 在哪里?A  char2在哪里?A  *char2在哪里?A  pChar3在哪里?A   *pChar3在哪里?D  ptr1在哪里?A   *ptr1在哪里?C

这些都是上述的答案,全部是关于各种类型的数据在C++中的存放位置。

【说明】
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
4. 数据段--存储全局数据和静态数据。
5. 代码段--可执行的代码/只读常量。

C语言中动态内存管理方式:malloc/calloc/realloc/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(p2)吗?
free(p3 );
}

相信大家对malloc与calloc非常熟悉,唯一的区别就是参数不同,还有就是calloc给予开辟空间初始化,而malloc却没有。realloc是对calloc与malloc进行扩容的,扩容分为异地扩容与原地扩容,当目标位置空间足够时会进行原地扩容,反之如果不够将进行异地扩容。

博主在之前的博客中详细讲解了C语言中的动态内存开辟,如果有疑问可以点击下面链接进行学习:C语言中动态内存管理方式:malloc/calloc/realloc/free icon-default.png?t=N7T8https://blog.csdn.net/m0_74755811/article/details/131820896?spm=1001.2014.3001.5501

C++内存管理方式

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

new/delete操作内置类型

void Test()
{
 // 动态申请一个int类型的空间
 int* ptr4 = new int;
 // 动态申请一个int类型的空间并初始化为10
 int* ptr5 = new int(10);
 // 动态申请10个int类型的空间
 int* ptr6 = new int[3];
 delete ptr4;
 delete ptr5;
 delete[] ptr6;
}

通过上述代码可以看出,使用new进行申请空间非常简单,只需要new加上类型即可。如果我们想进行初始化即可在后面加上(n)即可。当进行开辟多个内存空间时,我们像申请数组一样进行申请即可。但是在多个内存释放时,一定要加上[]。

看到现在我们觉得malloc与new的功能差不多呀,最多就是少些一些字母,那为什么C++还要创造一个新的字符进行学习呢?我们接着往下看:

new和delete操作自定义类型

class A
{
public:
	A(int a = 0)
		: _a(a)
	{
		cout << "A():" << this << endl;
	}

	~A()
	{
		cout << "~A():" << this << endl;
	}
private:
	int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
//还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}

 在内置类型中new与malloc是没有任何区别的,而在自定义类型中就会体现出极大的不同。在自定义类型中malloc不会对开辟的成员对象进行初始化,而new会自动调用构造函数。而在结束时delete会调研析构函数。所以说new与delete关键字就是为C++面向对象而产生的!!!

operator new与operator delete函数

 new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间。

/*
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);
}
/*
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)

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

那我们就会有疑问,new的底层逻辑就是malloc,delete的底层逻辑就是free。那么我们使用new开辟的空间能不能使用free进行释放呢?

答案是:可以,但最好不要交叉使用。因为有时候程序会正常进行,但是有时候就会报错。这是为什么呢?

当我们使用new申请一块非常简单的空间,只有一些基本的变量,最多就是少调用了一层析构函数,并不会影响空间的释放。但是当我们进行比如栈的开辟创建:

class Stack
{
public:
	Stack()
	{
		cout << "Stack()" << endl;
		_a = new int[4];
		_top = 0;
		_capacity = 4;
	}

	~Stack()
	{
		cout << "~Stack()" << endl;
		delete[] _a;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};
int main()
{
    Stack st;
    Stack* pst = new Stack;
    delete pst;
    return 0;
}

第一种情况是指针指向在堆开好的空间,只有两层关系,而使用new进行开辟先在堆上开辟对象空间,对象在使用构造函数进行初始化在堆上再开一层空间,是三层的关系。如果我们要使用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 T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对
象空间的申请
2. 在申请的空间上执行N次构造函数
delete[]的原理
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释
放空间 

定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没
有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A;  // 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
 return 0;
}

C++与new的使用场景 

虽然malloc与new都是在堆上进行开辟空间,但是他们获取内存的方式不一样。malloc是需要多少就索取多少,而new是”提前预支“内存,这样就可以提高new的效率,但是却导致了new空间浪费。所以说有利有弊,我们应该在适当的情况使用适当的做法。

C++中使用malloc和new有不同的用途和行为,你可以根据需要选择哪个更适合你的情况。以下是一些情况下的推荐用法:
使用malloc的情况:

1.C兼容性: 如果你编写的是C++代码,并且需要与C库或其他C代码进行交互,使用malloc可能更合适,因为malloc是C标准库函数。
2.需要手动管理构造和析构: malloc只分配内存,不会自动调用构造函数或析构函数。如果你需要手动控制对象的构造和析构过程,或者分配的内存不是用于存储对象(例如分配原始字节数组),则使用malloc。
3.需要明确指定内存大小: malloc接受一个字节数作为参数,而new会考虑类型的大小和额外的构造函数开销。如果你需要确切控制内存分配的字节数,可以使用malloc。
4.不需要类型检查: new是类型安全的,而malloc不是。如果你需要执行类型不安全的操作,可能需要使用malloc。

使用new的情况:

5.C++对象分配: 如果你需要分配内存以存储C++对象,通常应使用new或new[]。new会自动调用对象的构造函数,new[]用于动态分配数组,并在必要时调用构造函数。
6.类型安全: new提供了类型安全性,可以避免一些常见的内存错误,例如内存泄漏和越界访问。
7.更简洁的语法: new和new[]的语法更简洁,不需要显式指定分配的字节数。
8.自动内存管理: new分配的内存会在对象的生命周期结束时自动释放,从而减少了内存泄漏的风险。

总的来说,如果你编写纯粹的C++代码并需要分配内存以存储对象,通常建议使用new或new[],因为它们提供更好的类型安全性和内存管理。使用malloc通常是在需要更底层的内存分配控制,或者与C代码进行交互时的情况。无论使用哪种方法,都需要谨慎管理内存,确保在不再需要时释放它,以避免内存泄漏。


以上就是本次全部内容,感谢大家观看!!! 

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

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

相关文章

【C++】一篇文章搞懂auto关键字及其相关用法!

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

C语言基础篇1:数据类型、常量、变量

1 C语言基础 1.1 关键字 在C语言中&#xff0c;关键字是指被赋予特定意义的一些单词&#xff0c;不能把这些单词作为标识符来使用.C语言一共有32个关键字&#xff0c;如下图。在后面的学习中会逐渐接触到这些关键字的具体使用用法。 1.2 标识符 标识符可以简单的理解为一个名字…

第四届辽宁省大学生程序设计竞赛(正式赛)(12/13)

AC情况 赛中通过赛后通过暂未通过A√B√C√D○E○F√G√H√I○J√K—L√M√ 整体体验 easy&#xff1a;ABFHL mid&#xff1a;MJGC hard&#xff1a;IDKE 心得 感觉出了一堆典题&#xff0c;少数题还有些意思&#xff0c;E题确实神仙 题解 A. 欢迎来到辽宁省赛&#x…

bff层解决了什么痛点

bff层 -- 服务于前端的后端 什么是bff&#xff1f; Backend For Frontend&#xff08;服务于前端的后端&#xff09;&#xff0c;也就是服务器设计API的时候会考虑前端的使用&#xff0c;并在服务端直接进行业务逻辑的处理&#xff0c;又称为用户体验适配器。BFF只是一种逻辑…

基于爬行动物算法的无人机航迹规划-附代码

基于爬行动物算法的无人机航迹规划 文章目录 基于爬行动物算法的无人机航迹规划1.爬行动物搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用爬行动物算法来优化无人机航迹规划。 …

Python|OpenCV-图像的添加和混合操作(8)

前言 本文是该专栏的第8篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用OpenCV库对图像操作的时候,有时需要对图像进行运算操作,类似于加法,减法,位操作等处理。而本文,笔者将针对OpenCV对图像的添加,混合以及位操作进行详细的介绍说明和使用。 下面,…

03、SpringBoot + 微信支付 ---- 创建订单、保存二维码url、显示订单列表

目录 Native 下单1、创建课程订单保存到数据库1-1&#xff1a;需求&#xff1a;1-2&#xff1a;代码&#xff1a;1-3&#xff1a;测试结果&#xff1a; 2、保存支付二维码的url2-1&#xff1a;需求&#xff1a;2-2&#xff1a;代码&#xff1a;2-3&#xff1a;测试&#xff1a;…

python 之 sorted 函数

文章目录 sorted() 函数的语法返回值使用示例&#xff1a;示例 1&#xff1a;基本使用示例 2&#xff1a;指定降序排序示例 3&#xff1a;使用 key 参数进行自定义排序 注意事项&#xff1a; sorted() 是 Python 中的一个内置函数&#xff0c;用于对可迭代对象进行排序&#xf…

jquery之checkbox全选反选提交参数

实现效果 <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>Checkbox操作示例</title><script src"https://code.jquery.com/jquery-3.5.1.min.js"></script><script>$(document).ready(…

FPGA高端项目:图像缩放+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明本项目特点 2、相关方案推荐我这里已有的 GT 高速接口解决方案我这里已有的以太网方案我这里已有的图像处理方案 3、设计思路框架设计框图视频源选择ADV7611 解码芯片配置及采集动态彩条跨时钟FIFO图像缩放模块详解设计框图代码框图2种插值算法的整合与选择…

【数据结构与算法】JavaScript实现哈希表

文章目录 一、哈希表简介1.1.认识哈希表1.2.哈希化的方式1.3.解决冲突的方法1.4.寻找空白单元格的方式线性探测二次探测再哈希化 1.5.不同探测方式性能的比较1.6.优秀的哈希函数快速计算均匀分布 二、初步封装哈希表2.1.哈希函数的简单实现2.2.创建哈希表2.3.put(key,value)2.4…

时间序列预测模型实战案例(七)(TPA-LSTM)结合TPA注意力机制的LSTM实现多元预测

论文地址->TPA-LSTM论文地址 项目地址-> TPA-LSTM时间序列预测实战案例 本文介绍 本文通过实战案例讲解TPA-LSTM实现多元时间序列预测&#xff0c;在本文中所提到的TPA和LSTM分别是注意力机制和深度学习模型,通过将其结合到一起实现时间序列的预测&#xff0c;本文利用…

Google发布移动终端对象检测模型——mediapipe,无GPU依然飞快

对象检测模型最出名的当选YOLO系列,其YOLO系列已经更新到V8系列,但是现有的YOLO模型面临限制,如量化支持不足和准确性延迟权衡不足。 YOLO-NAS模型在包括COCO、Objects365和Roboflow 100在内的知名数据集上进行了预训练,使其非常适合生产环境中的下游对象检测任务。YOLO-NA…

unity【动画】脚本_角色动画控制器 c#

首先创建一个代码文件夹Scripts 从人物角色Player的基类开始 创建IPlayer类 首先我们考虑到如果不挂载MonoBehaviour需要将角色设置成预制体实例化到场景上十分麻烦&#xff0c; 所以我们采用继承MonoBehaviour类的角色基类方法写代码 也就是说这个脚本直接绑定在角色物体…

Quartz之JDBC-JobStoreTX配置

一、前言 上篇 《Quartz介绍》中使用的是RAMJobStored存储调度信息&#xff0c;当进程终止调度信息会丢失&#xff0c;本篇我们介绍使用JDBCJobStore来存储调度信息&#xff08;jobs、Triggers和日历&#xff09;。 二、Quartz 表结构 可以从官网&#xff08;http://www.qua…

康耐视深度学习ViDi-ViDi四大工具介绍与主要用途

Cognex ViDi 工具是一系列机器视觉工具&#xff0c;通过深度学习解决各种难以解决的挑战。虽然这些工具共享一个引擎&#xff0c;但它们在图像中寻找的内容不同。更具体地说&#xff0c;在分析单个点、单个区域或完整图像时&#xff0c;每个工具都有不同的侧重点。 Locate&…

JUC并发编程系列(一):Java线程

前言 JUC并发编程是Java程序猿必备的知识技能&#xff0c;只有深入理解并发过程中的一些原则、概念以及相应源码原理才能更好的理解软件开发的流程。在这篇文章中荔枝会梳理并发编程的基础&#xff0c;整理有关Java线程以及线程死锁的知识&#xff0c;希望能够帮助到有需要的小…

30、JAVA进阶——Socket编程

✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:乐趣国学的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:JAVA开发者成长之路 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:JAVA进阶——Socket编程 更多内容点击👇 …

idea2023 PoJie以后无法修改内存无效

1. 打开电脑环境变量 2. 找到对应pojie文件 vmoptions目录 3. 修改这个文件 添加或者修改配置 -Xms128m -Xmx8192m4. 重启idea 修改成功

flutter开发报错The instance member ‘widget‘ can‘t be accessed in an initializer

文章目录 问题描述问题原因解决方法 问题描述 The instance member ‘widget’ can’t be accessed in an initializer. 问题原因 “The instance member ‘widget’ can’t be accessed in an initializer” 错误是因为在初始化器列表中&#xff08;constructor initializer…