【C++】动态内存

一、内存区域分布

        首先我们来看一段代码并尝试解决以下问题:

 

  • 1. GlobalVar是全局变量,存储在数据段(静态区),选C。
  • 2. staticGlobalVar是静态全局变量,也存储在数据段(静态区),选C。
  • 3. staticVar是静态的局部变量,存储在数据段(静态区),选C。
  • 4. localVar是局部变量,存储在栈区当中,选A。
  • 5. num1是一个局部变量(存储着数组首元素的地址),存储在栈区中,选A。
  • 6. char2与num1相同,是局部变量,选A。
  •     *char2表示字符数组中的字符‘a’(即访问的是数组0下标的地址),存储在栈区中,选A。
  • 7. pchar3是指向常量字符串首字符的指针,是一个局部变量,选A。
  •     *pchar3表示常量字符串中的字符‘a’,属于常量,存储在代码段(常量区),选D。
  • 8. ptr1是一个局部指针变量,存储在栈区,选A。
  •     *ptr1是动态开辟的内存区域中的值,存储于堆区中,选B。

这里重点关注一下*char2和*pchar3的存储位置:

  • char2 所在语句的含义是将字符串"abcd"存储到字符数组中,本质是存储到了变量当中,所以解引用之后得到的字符肯定是在栈区中存储的;
  • pchar3 所在语句是将常量字符串"abcd"中首字符的地址存放于指针变量当中,该指针变量的值是存储在栈区当中的,但是解引用之后得到的字符是常量,所以*pchar3肯定存储于常量区。

接下来,我们画图表示一下内存区域的分布   c++的动态内存分配与c语言相同,也是在堆区中进行操作的。

二、c++中的动态内存管理方式

 之前在c语言当中,我们使用malloc/calloc/realloc/free函数来实现动态内存管理,但由于使用方式较为麻烦(例如要手动计算申请的内存大小、检查返回值等)

所以c++引入了两个操作符,便于我们更高效地实现动态内存管理:new和delete。

        接下来我们从代码角度来解释这两个关键字的使用方法。

1. new与delete对内置类型的操作 

int main()
{int* p1 = new int;//动态申请一个int类型的空间int* p2 = new int(10);//动态申请一个int类型的空间,并初始化为10int* p3 = new int[10];//动态申请10个int类型的空间int* p4 = new int[10] {10};//动态申请10个int类型的空间,并将第一个元素初始化为10,其余元素为0//这里的初始化规则与数组定义时的初始化相同//释放内存delete p1;delete p2;delete[] p3;delete[] p4;return 0;
}

注意:当我们释放连续的空间时,delete之后要加上“ [ ] ”

可以看到,我们使用new操作符申请内存时,不仅不用sizeof来计算申请所需的空间大小,而且还能对申请的空间进行初始化,十分方便。

2. new与delete对自定义类型的操作

new和delete申请和释放自定义类型的空间与内置类型的语法相同

当我们使用new/delete操作自定义类型时,它们与malloc/free最大的区别是:

  • new在申请内存空间之后还会调用构造函数对该空间进行初始化;
  • delete会调用析构函数,然后释放内存空间。
  • 而malloc/free只会开辟空间,并不会调用这两种函数。

代码示例:

#include <iostream>
using namespace std;class A
{
public:A(int a = 10, int c = 20):_a(a),_c(c){cout << "调用构造函数" << endl;}~A(){cout << "调用析构函数" << endl;}void Print() const{cout << _a << endl;cout << _c << endl;}
private:int _a;int _c;
};int main()
{A* p = new A{ 3,5 };//动态申请一个A类型的空间,并且调用构造函数初始化p->Print();//打印内容delete p;//调用析构函数,然后释放空间return 0;
}

三、operator new函数和operator delete函数

  operator new函数和operator delete函数是c++提供的全局函数,当我们使用new或者delete操作符时,它们就会调用这两个函数来实现相关功能。以下是这两个函数的底层实现

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);
}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;
}/*可以看到,在operator new函数体当中,首先调用了malloc函数来申请内存空间,如果申请成功则直接返回,否则就会启动相关应对措施;在operator delete函数体中,我们可以看到一个函数“_free_dbg”,它其实就是free的底层实现。
*/

所以说 operator new 使用了 malloc 来开辟内存,operator delete 使用了 free 来释放内存,只不过在其中添加了一些异常处理机制等,使得内存开辟更加完善。

  了解了这两个函数的运行机制,我们由此总结出newdelete的实现原理: 

四、new和delete的实现原理

1. 内置类型

对于内置类型而言,new/delete与malloc/free基本类似,不同的地方是:

在申请空间时可以初始化,并且new在空间申请失败时会抛出异常,而malloc返回空指针

2. 自定义类型

对于自定义类型,它的实现逻辑就比较复杂了,我们逐一分析:

1. new

首先调用operator new函数申请内存空间,然后调用构造函数,完成初始化

2. delete

首先调用析构函数,对开辟的内存进行资源清理,然后调用operator delete函数释放内存

3. new[ ]

首先调用 operator new[ ] 函数申请多个对象的内存空间(该函数中调用了operator new),然后调用N次构造函数,完成初始化

4.delete[ ]

首先调用N次析构函数清理资源,然后调用 operator delete[ ] 函数释放空间(该函数中调用了operator delete)

五、定位new表达式

        我们都知道,当对象被创建的时候,会自动调用构造函数。那么我们能否在一块已有的内存区域上显示调用构造函数构造对象呢?语法上是不允许显示调用构造函数的,但是定位new表达式可以做到:

class A
{
public:A(int a = 10, int c = 20):_a(a),_c(c){cout << "调用构造函数" << endl;}~A(){cout << "调用析构函数" << endl;}void Print() const{cout << _a << endl;cout << _c << endl;}int _a;int _c;
};int main()
{A* p = (A*)malloc(sizeof(A));//申请内存空间,但不调用构造函数new(p)A();//使用定位new表达式调用构造函数p->Print();//打印一下成员p->~A();//显示调用析构函数return 0;
}

 

可以看到,我们成功使用定位new表达式调用了构造函数并且为成员变量设置初始值。定位new表达式在实现内存池或缓存区等高级内存管理策略时非常有用。定位new表达式的语法是:

new(ptr) Class(参数)

这里的ptr表示指向该内存区域的指针,Class是类名

当构造函数中有非缺省参数时,需要我们在类名之后的括号中传参。


定位new表达式的注意事项:

1. 常规new表达式既负责分配内存,还负责构造对象;而定位new表达式只负责构造对象。所以在使用定位new表达式之前,要确保以及分配好足够的内存。

2. 使用定位new表达式调用构造函数后,如果我们不再使用该对象,要记得主动调用其析构函数并释放内存。

六、malloc/free和new/delete的区别总结

共同点

都是从堆区申请空间,并且使用结束后要进行手动释放。

不同点

1. malloc和free是函数,而new和delete是操作符

2. malloc申请的空间不会进行初始化,而new可以初始化

3. malloc申请空间时,需要手动计算申请的空间大小;而new申请时只需要说明类型与个数即可。

4. malloc申请空间返回的指针是void* 类型,需要进行强转,而new不需要。

5. malloc申请失败会返回NULL,所以使用时必须检查返回值;而new申请失败会抛出异常,无需检查返回值。

6. 对于自定义类型空间的开辟,malloc和free只会开辟/销毁对应的内存空间,而new和delete会调用构造函数/析构函数完成初始化/资源清理操作

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

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

相关文章

基于STM32的温湿度监测器教学

引言 随着科技的发展&#xff0c;温湿度监测在农业、仓储、环境监测等领域的应用越来越广泛。本文将指导您如何基于STM32开发一个简单的温湿度监测器&#xff0c;使用常用的DHT11或DHT22传感器进行数据采集&#xff0c;并将监测结果显示在LCD或OLED屏幕上。 项目名称 STM32温湿…

哈希表,哈希桶及配套习题

我们今天带大家简单了解哈希表是怎样的&#xff0c;和简单模拟哈希桶&#xff0c;还有几道练习题 一&#xff0c;哈希表 什么是哈希表&#xff0c;哈希表是一种非常非常高效的数据结构&#xff0c;它用来搜索我们想要的数据&#xff0c;我们之前学过很多查找方法&#xff0c;最…

二百七十四、Kettle——ClickHouse中对错误数据表中进行数据修复(实时)

一、目的 在完成数据清洗、错误数据之后&#xff0c;需要根据修复规则对错误数据进行修复 二、Hive中原有代码 insert into table hurys_db.dwd_queue partition(day) selecta3.id,a3.device_no,a3.source_device_type,a3.sn,a3.model,a3.create_time,a3.lane_no,a3.lane_…

Golang | Leetcode Golang题解之第530题二叉搜索树的最小绝对差

题目&#xff1a; 题解&#xff1a; func getMinimumDifference(root *TreeNode) int {ans, pre : math.MaxInt64, -1var dfs func(*TreeNode)dfs func(node *TreeNode) {if node nil {return}dfs(node.Left)if pre ! -1 && node.Val-pre < ans {ans node.Val -…

Android Studio打包时不显示“Generate Signed APK”提示信息

Android Studio打包时&#xff0c;默认显示“Generate Signed APK”提示信息&#xff0c;如下图所示&#xff1a; 如果在打包时不显示“Generate Signed APK”提示信息&#xff0c;解决办法是&#xff1a; Android Studio菜单栏&#xff0c;“File->Settings->Appearan…

手游和应用出海资讯:可灵AI独立APP即将上架;Rollic在英国推出芭比合并解谜手游

NetMarvel帮助游戏和应用广告主洞察全球市场、获取行业信息&#xff0c;以下为10月第四周资讯&#xff1a; ● 苹果开发全新游戏中心应用 ● Meta计划开发人工智能搜索引擎 ● 微软已拥有20个游戏IP&#xff0c;收入达10亿美元 ● OpenAI计划在12月推出其下一代前沿模型Orion ●…

js中多let与var

在 JavaScript 中&#xff0c;let 和 var 都用于声明变量&#xff0c;但它们有一些关键的区别。主要区别包括作用域、变量提升、可重复声明、以及在全局作用域中的行为。 1. 作用域&#xff08;Scope&#xff09; let&#xff1a;块级作用域。用 let 声明的变量只在其所在的代…

qt管理系统框架(好看界面、漂亮界面、好看的界面、漂亮的界面)

概述 最近一个项目用QT开发&#xff0c;然后找了美工帮设计了下界面。总算完工&#xff0c;后想一下干脆抽出一个基础框架&#xff0c;方便以后用。 功能 支持mysql、echarts。 支持加载动态权限菜单&#xff0c;轻松权限控制。 支持遮罩对话框、抽屉 支持开机启动动画界面 内…

华为云计算知识总结——及案例分享

目录 一、华为云计算基础知识二、华为云计算相关案例实战案例一&#xff1a;搭建弹性云服务器&#xff08;ECS&#xff09;并部署Web应用案例二&#xff1a;构建基于OBS的图片存储和分发系统案例三&#xff1a;基于RDS的高可用数据库应用案例四&#xff1a;使用华为云DDoS防护保…

11.1组会汇报-基于区块链的安全多方计算研究现状与展望

基础知识 *1.背书&#xff0c;这个词源来自银行票据业务&#xff0c;是指票据转让时&#xff0c;原持有人在票据背面加盖自己的印鉴&#xff0c;证明该票据真实有效、如果有问题就可以找原持有人。 区块链中的背书就好理解了。可以简单的理解为验证交易并声明此交易合法&…

【Linux】进程间通信(命名管道、共享内存、消息队列、信号量)

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;Linux 创作时间 &#xff1a;2024年11月2日 命名管道&#xff1a; 如果我们想在不相关的进程之间交换数据&#xff0c;可以使用FIFO文件来做这项工作&#xff0c;它经常被称为命名管道。命名管道是一种特殊类型的文…

划界与分类的艺术:支持向量机(SVM)的深度解析

划界与分类的艺术&#xff1a;支持向量机&#xff08;SVM&#xff09;的深度解析 1. 引言 支持向量机&#xff08;Support Vector Machine, SVM&#xff09;是机器学习中的经典算法&#xff0c;以其强大的分类和回归能力在众多领域得到了广泛应用。SVM通过找到最优超平面来分…

Java设计模式(代理模式整理中ing)

一、代理模式 1、代理模式定义&#xff1a; 代理模式&#xff1a;由于某些原因要给某对象提供一个代理以控制对该对象的访问&#xff0c;这时访问对象不适合或者不能够直接引用目标对象&#xff0c;代理对象作为访问对象与目标对象之间的中介进行连接调控调用。 2、代理模式的…

【含文档+源码】基于SpringBoot+Vue的新型吃住玩一体化旅游管理系统的设计与实现

开题报告 本文旨在探讨新型吃住玩一体化旅游管理系统的设计与实现。该系统融合了用户注册与登录、旅游景点管理、旅游攻略发帖、特色旅游路线推荐、附近美食推荐以及酒店客房推荐与预定等多项功能&#xff0c;旨在为游客提供全方位、一体化的旅游服务体验。在系统设计中&#…

如何卸载电脑上的软件?彻底删除第三方和系统自带软件方法!(新款)

如何卸载电脑上的软件&#xff1f;在日常使用电脑的过程中&#xff0c;我们经常会安装各种软件以满足不同的需求。然而&#xff0c;随着时间的推移&#xff0c;一些不再使用的软件可能会占用系统资源&#xff0c;影响电脑性能。因此&#xff0c;定期卸载不需要的软件是保持系统…

cocos开发QA

目录 TS相关foreach循环中使用return循环延迟动态获取类属性 Cocos相关属性检查器添加Enum属性使用Enum报错 枚举“XXX”用于其声明前实现不规则点击区域使用cc.RevoluteJoint的enable激活组件无效本地存储以及相关问题JSON.stringify(map)返回{}数据加密客户端复制文本使用客户…

LeetCode :21. 合并两个有序链表(Java)

目录 题目描述: 代码: 第一种: 第二种: 题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; …

删除的文件怎么找回?删除文件恢复全面指南

我们常常在日常生活或工作中不小心删除了重要文件&#xff0c;这样的情况可能瞬间让人感到无助。不过&#xff0c;数据恢复技术已相当成熟&#xff0c;我们可以通过多种方法来找回误删的文件。下面我们将从简单到复杂逐步讲解找回删除文件的方法&#xff0c;希望可以帮助大家在…

D57【python 接口自动化学习】- python基础之异常

day57 异常捕获 学习日期&#xff1a;20241103 学习目标&#xff1a;异常 -- 73 异常捕获&#xff1a;出现异常时&#xff0c;如何利用程序进行处理&#xff1f; 学习笔记&#xff1a; try-except代码块 # 捕获异常 num1 num10 try:num/num1except Exception as e:print(上…

【06】A-Maven项目SVN设置忽略文件

做Web项目开发时&#xff0c;运用的是Maven管理工具对项目进行管理&#xff0c;在项目构建的过程中自动生成了很多不需要SVN进行管理的文件&#xff0c;SVN在对源码进行版本管理时&#xff0c;需要将其忽略&#xff0c;本文给出了具体解决方案。 SVN设置忽略Maven项目中自动生成…