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

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类型的空间并初始化为10int* 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_FINALLYreturn;
}
/*
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.mzph.cn/news/164707.shtml

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

相关文章

CCC联盟——UWB MAC(一)

本文在前面已经介绍了相关UWB的PHY之后&#xff0c;重点介绍数字钥匙&#xff08;Digital Key&#xff09;中关于MAC层的相关实现规范。由于MAC层相应涉及内容比较多&#xff0c;本文首先从介绍UWB MAC的整体框架&#xff0c;后续陆续介绍相关的网络、协议等内容。 1、UWB MAC架…

真心的表扬与鼓励,胜过一万句说教

今天我想和大家分享一下&#xff0c;怎样跟孩子运用鼓励和表扬。我记得鲁道夫德雷克斯是阿德勒学派的心理学家&#xff0c;也是来自《孩子的挑战》一书的作者&#xff0c;他说孩子们需要鼓励&#xff0c;就像植物需要水&#xff0c;鼓励能让孩子知道自己做的事与自己是什么样的…

非自定义Bean注解开发Bean配置类的注解开发

目录 非自定义Bean注解开发 Bean配置类的注解开发 非自定义Bean注解开发 非自定义的Bean不能像自定义Bean使用Component进行管理&#xff0c;非自定义Bean要通过工厂的方式进行实例化&#xff0c;使用Bean标注方法即可&#xff0c;Bean的属性文beanName 如果Bean工厂方法需要参…

[23] 4K4D: Real-Time 4D View Synthesis at 4K Resolution

paper | proj | code 提出一种基于K-Planes的4D point cloud Representation&#xff1b;提出一种Hybrid appearance model&#xff0c;包含image blending model和SH model。其中&#xff0c;image blending model将3D点映射回原图中求得&#xff0c;SH model通过模型预测求得…

【工具栏】热部署不生效

目录 配置热部署&#xff1a; 解决热部署不生效&#xff1a; 首先检查&#xff1a; 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 配置热部署&#xff1a; https://blog.csdn.net/m0_67930426/article/details/133690559 解决热部署不…

Python中的解析器argparse

import argparse## 构造解析器 argparse.ArgumentParser() parse argparse.ArgumentParser(description"caculateing the area of rectangle")## 添加参数 .add_argument() parse.add_argument("--length",typeint,default20,helpThe length of rectangle…

【追求卓越09】算法--散列表(哈希表)

引导 通过前面几个章节的学习&#xff08;二分查找&#xff0c;跳表&#xff09;&#xff0c;我们发现想要快速查找某一个元素&#xff0c;首先需要将所有元素进行排序&#xff0c;再利用二分法思想进行查找&#xff0c;复杂度是O(logn)。有没有更快的查找方式呢&#xff1f; 本…

微软发布最新.NET 8长期支持版本,云计算、AI应用支持再强化

11 月 15 日开始的为期三天的 .NET Conf 在线活动的开幕日上&#xff0c;.NET 8作为微软的开源跨平台开发平台正式发布。.NET 团队着重强调云、性能、全栈 Blazor、AI 和 .NET MAUI 是.NET 8的主要亮点。.NET团队在 .NET Conf 2023 [1]活动开幕式上表示&#xff1a;“通过这个版…

nginx 模块相关配置及结构理解

文章目录 模块配置结构模块配置指令先看一下 ngx_command_t 结构一个模块配置的demo简单模块配置的案例演示 模块上下文结构模块的定义 模块配置结构 Nginx中每个模块都会提供一些指令&#xff0c;以便于用户通过配置去控制该模块的行为。 Nginx的配置信息分成了几个作用域(sc…

使用注解的AOP编程

使用注解的AOP编程 当注解没有参数时 当使用注解进行面向切面编程&#xff08;AOP&#xff09;时&#xff0c;你可以按照以下步骤来实现&#xff1a; 步骤&#xff1a; 1. 创建自定义注解&#xff1a; 首先&#xff0c;创建自定义的注解&#xff0c;以便在代码中标记需要进…

Excel换不了行怎么解决?

方法一: 使用Alt Enter键 在Excel中&#xff0c;输入文字时按下回车键&#xff0c;光标将会移到下一个单元格&#xff0c;如果想要换行&#xff0c;可以尝试使用Alt Enter键。具体操作如下: 1.在单元格中输入文字; 2.想要换行时&#xff0c;在需要换行的位置按下Alt Enter键; 3…

延时任务定时发布,基于 Redis 与 DB 实现

目录 1、什么是延时任务&#xff0c;分别可以使用哪些技术实现&#xff1f; 1.2 使用 Redis 和 DB 相结合的思路图以及分析 2、实现添加任务、删除任务、拉取任务 3、实现未来数据的定时更新 4、将数据库中的任务数据&#xff0c;同步到 Redis 中 1、什么是延时任务&#xff…

网络运维与网络安全 学习笔记2023.11.23

网络运维与网络安全 学习笔记 第二十四天 今日目标 VRRP负载均衡、BFD原理与配置、BFD典型应用 DHCP工作原理、全局模式DHCP VRRP负载均衡 VRRP单组缺陷 每网段存在一个VRRP组&#xff0c;缺点如下&#xff1a; 主网关数据转发压力大 备份网关不转发任何数据 网络设备利用…

Hook技术(钩子技术)

HOOK&#xff08;钩子技术&#xff09; 这里的hook我理解的意思就是通过拦截指令&#xff0c;将指令换成自己想要的指令&#xff0c;从而做道绕过原本的程序指令&#xff0c;要修改这个指令&#xff0c;要用汇编技术&#xff0c;从二进制入手。 扩展&#xff1a; 木马病毒之…

git clone慢的解决办法

在网站 https://www.ipaddress.com/ 分别搜索&#xff1a; github.global.ssl.fastly.net github.com 得到ip&#xff1a; 打开hosts文件 sudo vim /etc/hosts 在hosts文件末尾添加 140.82.114.3 github.com 151.101.1.194 github.global-ssl.fastly.net 151.101.65.194 g…

外部网关协议_边界网关协议BGP

一.边界网关协议BGP的基本概念 边界网关协议(Border Gateway Protocol&#xff0c;BGP&#xff09;属于外部网关协议EGP这个类别&#xff0c;用于自治系统AS之间的路由选择协议。由于在不同AS内度量路由的“代价”(距离、带宽、费用等&#xff09;可能不同&#xff0c;因此对于…

elasticsearch 7安装

问题提前报 max virtual memory areas error max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 如果您的环境是Linux&#xff0c;注意要做以下操作&#xff0c;否则es可能会启动失败 1 用编辑工具打开文件/etc/sysctl.conf 2 …

qml渲染引擎介绍

qml项目启动入口 Qt Quick项目qml脚本在C++代码里启动,main.cpp如下: #include <QGuiApplication> #include <QQmlApplicationEngine>int main(int argc, char *argv[]) {

VUE excel表格导出

js代码 //下载模板 downloadExl() { // 标题 const tHeader [‘xxx’,xxx,xx名称,电枪xx,协议xx,snxx]; // key const filterVal [agentName, stationName, equName, channelNumber, manufacturer, sn, ]; // 值 const datas [ { agentName: 你好, stationName: 我们, e…

激光雷达与惯导标定 | Lidar_IMU_Init : 编译

激光雷达与惯导标定&#xff1a;Lidar_IMU_Init 编译 功能包安装安装ceres-solver-2.0.0 &#xff08;注意安装2.2.0不行&#xff0c;必须要安装2.0.0&#xff09; LI-Init是一种鲁棒、实时的激光雷达惯性系统初始化方法。该方法可校准激光雷达与IMU之间的时间偏移量和外部参数…