深度解读《深度探索C++对象模型》之C++的临时对象(二)

目录

临时对象的生命期

特殊的情况


接下来我将持续更新“深度解读《深度探索C++对象模型》”系列,敬请期待,欢迎左下角点击关注!也可以关注公众号:iShare爱分享,或文章末尾扫描二维码,自动获得推文和全部的文章列表。

上一篇中讲解了C++编译器在什么情况下会产生临时对象,上一篇请从这里阅读:

深度解读《深度探索C++对象模型》之C++的临时对象(一) 

这一篇来讲解临时对象的生命周期,上篇讲解何时产生临时对象,这篇讲解临时对象存在的时间以及何时会被销毁。

临时对象的生命期

        C++标准里规定:为某表达式创建了一个临时对象,则此临时对象将一直存在直到包含有该表达式的最大的表达式计算完成为止。怎么理解这句话?我们以一行代码来解释,如下的代码:

// 假设verbose为bool类型变量,根据这个开关输出不同的信息
// a,b,c,d皆为string类型的对象。
verbose ? a + b : c + d;

        上面的表达式即是一个完整的表达式,也是一个最大的表达式,里面包含三个子表达式:“verbose”,“a + b”,“ c + d”,其中“a + b”和“c + d”两个子表达式将会有可能产生临时对象,视verbose的测试结果将运算不同的子表达式。这个运算的中间过程产生的临时对象需要等到完整的表达式运算完成之后才能被销毁。我们还是以上面的测试代码为例,修改下main函数,其它代码不变,如下:

int main() {
    Object a;
    Object b;
    printf("a + b = %d\n", (int)(a + b));
    return 0;
}

        它生成的对应的汇编代码:

main:                                   # @main
  # 略...
  lea     rdi, [rbp - 8]
  call    Object::Object() [base object constructor]
  lea     rdi, [rbp - 16]
  call    Object::Object() [base object constructor]
  lea     rdi, [rbp - 32]
  lea     rsi, [rbp - 8]
  lea     rdx, [rbp - 16]
  call    operator+(Object const&, Object const&)
  lea     rdi, [rbp - 32]
  call    Object::operator int()
	# 略...
  call    printf@PLT
  lea     rdi, [rbp - 32]
  call    Object::~Object() [base object destructor]

        [rbp - 8]是对象a,[rbp - 16]是对象b,[rbp - 32]是临时对象。上面代码的第14行在调用printf函数之后才去调用了Object类的析构函数去析构临时对象。

        但当临时对象是根据程序的测试语句有条件的被产生出来时,临时对象的生命期规则就变得复杂了。还是以上面的例子,修改如下:

int main() {
    Object a;
    Object b;
    Object c;
    Object d;
    if ( a + b || c + d) { printf("Go here.\n"); }
    return 0;
}

        第6行的if语句中,子表达式“c + d”是否会被运算取决于前面的子表达式“a + b”的测试结果,只有它的结果为false的时候才会评估子表达式“c + d”,这时候才会产生一个临时对象。临时对象应该要销毁,但不是无条件的销毁,要根据它是否有被产生出来而决定。上面的if语句大概可以转换成以下的语句:

// 伪代码:
Object tmp1 = a + b;
int test1 = tmp1.operator int();
int test2 = 0;
if (test1 == 0) {
    Object tmp2 = c + d;
    test2 = tmp2.operator int();
    // 此处销毁临时对象tmp2?(1)
}
tmp1.Object::~Object();
// 此处销毁临时对象tmp2?(2)

        临时对象tmp2应该在哪里被销毁,在(1)处还是在(2)处?其实这两种处理方式都不准确,首先是在(1)处时还不能销毁,根据C++的标准规定,这时完整的表达式还没有运算完,此时还不能销毁临时对象。其次是(2)处的处理也不妥当,因为有可能tmp2并未被产生出来,它需要根据条件来决定是否销毁。来看看编译器是怎么做的,看看这行代码对应的汇编:

main:                                   # @main
  # 省略构造的代码,对象a,b,c,d的存放位置分别是:  
	#[rbp - 8],[rbp - 16],[rbp - 32],[rbp - 40]
  mov     byte ptr [rbp - 57], 0
  # 省略a+b的代码,产生的临时对象存放在[rbp - 48]
  lea     rdi, [rbp - 48]
  call    Object::operator int()
  mov     dword ptr [rbp - 64], eax       # 4-byte Spill
  mov     ecx, dword ptr [rbp - 64]       # 4-byte Reload
  mov     al, 1
  cmp     ecx, 0
  mov     byte ptr [rbp - 65], al         # 1-byte Spill
  jne     .LBB3_9
  # 省略c+d的代码,产生的临时对象存放在[rbp - 56]
  mov     byte ptr [rbp - 57], 1
  lea     rdi, [rbp - 56]
  call    Object::operator int()
  mov     dword ptr [rbp - 72], eax       # 4-byte Spill
  mov     eax, dword ptr [rbp - 72]       # 4-byte Reload
  cmp     eax, 0
  setne   al
  mov     byte ptr [rbp - 65], al         # 1-byte Spill
.LBB3_9:
  mov     al, byte ptr [rbp - 65]         # 1-byte Reload
  mov     byte ptr [rbp - 73], al         # 1-byte Spill
  test    byte ptr [rbp - 57], 1
  jne     .LBB3_10
  jmp     .LBB3_11
.LBB3_10:
  lea     rdi, [rbp - 56]
  call    Object::~Object() [base object destructor]
.LBB3_11:
  lea     rdi, [rbp - 48]
  call    Object::~Object() [base object destructor]
  mov     al, byte ptr [rbp - 73]         # 1-byte Reload
  test    al, 1

        编译器是以一个标志位来标记是否有临时对象的产生,见上面代码的第4行,标志位存放在[rbp - 57]中,占用一个byte的大小,默认值设置为0。

        然后接下来的第5到第13行包括省略掉的代码,是运算“a + b”然后对其转换成int型数据再进行测试,这里“a + b”的运算过程产生临时对象存放在[rbp - 48]。第10行的al保存的是条件测试的结果,也就是决定执行if后面的语句还是执行else后面的语句的作用,它也是一个byte大小,暂存在[rbp - 65]中。

        第13行就是根据“a + b”的结果是否继续评估后续的“c + d”,如果为0将继续评估“c + d”,第14行到第20行包括省略的代码就是运算“c + d”,这里将产生临时对象,存放在[rbp - 56]中,同时最主要的一点就是将记录是否产生临时对象的标志位设置为1,即代码的第15行,[rbp - 57]现在的值为1。第21行的setne指令的作用是取标志寄存器中ZF的值并取反后,再放到AL中,这个是决定整个if语句括号中的测试条件的真假。

        接下来从标签.LBB3_9开始的代码就是根据if语句里的条件测试结果决定是执行if语句还是else语句,以及根据标志位是否需要销毁临时对象。第26行就是测试[rbp - 57]的值是否为1,根据上面的分析,如果有评估到“c + d”这里的话就会产生临时对象并这个值被设置为1,这里判断为1的话就跳转到.LBB3_10标签处执行析构动作,如果为0则跳过这段代码。接着就是销毁“a + b”产生的临时对象(存放在[rbp - 48]),这个临时对象是一定会产生的,所以不必判断标志位,然后第36行就是根据if语句的测试结果决定接下来的代码流程。

特殊的情况

        上面提到的临时对象在完整的表达式运算完之后就会被销毁掉,但有两个例外的地方,它不会马上被销毁,而是会继续存在一段时间,例如:

  • 表达式被用来初始化一个对象时

        如下面的代码:

bool condition;
Object obj = condition ? a + b : c + d;

        其中“a + b”和“c + d”将根据测试结果产生出临时对象,根据规则临时对象在“?:”这个完整表达式结束后就可以被销毁,但这时需要用这个临时对象来初始化obj对象,所以不能马上销毁它,必须要等到初始化完obj之后才能销毁。

  • 当临时对象被一个引用绑定时

        如下面的代码:

const Object &ref = a + b;

        引用ref将绑定到一个“a + b”产生的临时对象上,编译器将它转换为如下的伪代码:

Object tmp = a + b;
const Object &ref = tmp;

        这种情况下,临时对象tmp不能被释放,否则ref将引用到一个空对象,临时对象将会一直被保留,直到绑定到它的引用ref的生命周期结束。


本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机keil编程中遇到的问题(持续更新)

字符无法打印报错 查看特殊功能寄存器名字的时候也会报错,因为无法编译通过,导致头文件的定义内容无法查找 keil编译中 error C127: ‘xx’: invalid storage class 这种一般是在编写头文件或源文件时,在声明函数的结尾没有添加分号&…

C++——list和string

list与string 前言一、listlist.hList的节点类List的迭代器类list类list.h 完整实现 list.cppList的节点类List的迭代器类list类list.cpp 完整实现 二、stringstring.hstring.cpp 总结 前言 C容器的学习开始啦! 大家先来学习list! 紧接着string和vector…

PGP加密技术:保护信息安全的利器

随着数字化时代的到来,个人和企业对信息安全的需求日益增长。PGP(Pretty Good Privacy)加密技术作为一项强大的加密工具,为保护敏感数据提供了一种有效的方法。本文将探讨PGP加密技术的基本原理、应用场景以及其在现代信息安全中的…

LeetCode:盛最多水的容器

文章收录于LeetCode专栏 盛最多水的容器 给你n个非负整数a1,a2,…,an,每个数代表坐标中的一个点(i, ai) 。在坐标内画 n 条垂直线,垂直线i的两个端点分别为(i, ai) 和 (i, 0)。找出其中的两条线,使得它们与…

如何实现网页上3D模型的展示、浏览和互动?

实现网页上3D模型的展示、浏览和互动,可以通过以下步骤进行: 1、创建3D内容:使用3ds max、Maya、blender、C4D等3D软件制作好3D模型。 2、设计3D应用:把制作好的模型导出为fbx、obj、dae、gltf、glb等格式文件,上传到…

不盖CNAS的证书就是无效的?证书哪些信息是“非必要”?

做设备校准的企业,大多数都是为了拿到仪器校准证书,而说起校准证书,很多人优先就是想到CNAS,CNAS作为校准行业重要的核心资质,无论是校准机构实力的证明,还是满足企业年审的需要,基本上都是关键…

Spring Security Oauth2 JWT 添加额外信息

目录 一、问题描述 二、实现步骤 1、自定义TokenEnhancer 2、配置授权服务器 3、自定义UserDetails的User类 三、参考文档 一、问题描述 Oauth2里默认生成的JWT信息并没有用户信息,在认证授权后一般会返回这一部分信息,我对此进行了改造。 Oauth…

大数据集成平台建设方案-word原件资料

基础支撑平台主要承担系统总体架构与各个应用子系统的交互,第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上,实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台,提出了以下要求: (1) 基于平台的基础架构&…

短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发

短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发 搭建短视频矩阵系统源码的交付步骤可以概括为以下几个关键环节: 1. **系统需求分析**:明确系统需要支持的功能,如短视频的上传、存储、播放、分享、评论、点赞等。 2. **技术选…

MySQL —— 数据类型

一、数值类型 以上表格整理了用来表示数值类型的数据类型,其中,接下来将介绍和展示其中几个类型的使用和各种细节 1.tinyint 越界测试:建立一个包含tinyint类型的表格,插入各中数据去查看结果,并且尝试插入边界数据和…

数字人捕捉、建模与合成

在感知系统中,我们与外部合作者一起创建逼真的 3D 人类,其行为可以像虚拟世界中的真实人类一样。这项工作在今天有许多实际应用,并且对于元宇宙的未来至关重要。但是,在感知系统中,我们的目标是科学的——通过重现人类…

PS五官与服装PSD文件大全,男女证件照制作必备素材

一、素材描述 男女证件照服装和五官等PSD文件大全,制作证件照的必备素材合集,轻松制作高端大气的证件照。什么是DR5?DR5是Delicious Retouch 5的简称,这是一款非常优秀的PS人像磨皮美容插件,DR5的主要功能就是针对人像…

AI换脸原理(4)——人脸对齐(关键点检测)参考文献2DFAN:代码解析

注意,本文属于人脸关键点检测步骤的论文,虽然也在人脸对齐的范畴下。 1、介绍 在本文中,重点介绍了以下几项创新性的成果,旨在为人脸关键点检测领域带来新的突破。 首先,成功构建了一个卓越的2D人脸关键点检测基线模型。这一模型不仅集成了目前最优的关键点检测网络结构,…

领导想提拔你,看的从来不只是努力

谁不曾做过努力工作,一路升职加薪的职业规划,现实却给很多人泼了一盆冷水。 在大家的普遍认知里,北上广普遍高薪,月入过万就是标配。 然而在逃离北上广的热门帖子下,有网友发出了声音: “我都35了&#xff…

XSS、CSRF、SSRF漏洞原理以及防御方式_xss csrf ssrf

这里写目录标题 XSS XSS攻击原理:XSS的防范措施主要有三个:编码、过滤、校正 CSRF CSRF攻击攻击原理及过程如下:CSRF攻击的防范措施: SSRF SSRF漏洞攻击原理以及方式SSRF漏洞攻击的防范措施 XMLXSS、CSRF、SSRF的区别 XSS、CSRF的…

落地护眼灯十大品牌哪款性价比高?品牌排行榜前十名全面揭晓!

落地护眼灯十大品牌哪款性价比高?落地护眼灯已经逐渐成为孩子日常使用率较高的电器之一,它的优点非常突出,对于学习、工作、绘画等环境都能够提供良好的健康环境,同时还携带多种智能调节功能,例如:入座感应…

Android 启动提示Android 正在升级...提示源码分析

正常情况下烧录的新机会有这个提示,因为系统启动时候要对系统APP做DexOpt优化,流程如下: 进入performBootDexOpt函数: 提示框代码如下: 而提示框的Tile和Msg如下: 打印Log: 觉得本文对…

小项目“谈笑风生”测试报告

文章目录 一、项目介绍1.1项目背景1.2功能介绍 二、测试环境三、测试执行过程3.1功能测试3.1.1登录页面测试3.1.2注册页面测试3.1.3主页面测试 3.2界面自动化测试3.2.1登录模块测试3.2.2注册模块测试3.2.3展示各种信息模块测试3.2.34聊天消息传送模块测试 四、测试结论与建议 一…

Ubuntu软件中心不显示

装完Ubuntu后没有Software -- 更新apt sudo apt update -- 升级apt sudo apt upgrade -- 重启 sudo systemctl reboot-- 安装snap sudo apt-get install snap -- 安装软件商店 sudo snap install snap-store -- 更新软件商店 sudo snap refresh snap-store安装成功&#xff01…

C语言,实现数字谱到简谱的转换(二)

C语言,实现数字谱到简谱的转换(二) 前言:本文初编辑于2024年5月8日 CSDN:https://blog.csdn.net/rvdgdsva 博客园:https://www.cnblogs.com/hassle 前言 结合前文使用 之前的程序默认C调4/4拍&#xff…