C++编译器的程序转化

编译器在某些情况下会对程序进行转化,有些是编译器需要的,有些是出于性能考虑的,转化可能会产生出乎意料的结果

文章目录

  • 明确的初始化操作
  • 参数的初始化
  • 返回值的初始化
  • 在使用者层面做优化
  • 在编译器层面做优化
    • NRV 优化
    • NRV优化的弊端


明确的初始化操作

已知有这样的定义

X x0;

下面有三个定义,每一个都明显地以 x0 来初始化其类对象

void foo_bar() {
	X x1( x0 );
	X x2 = x0;
	X x3 = X( x0 );
}

必要的程序转化有两个阶段:

  1. 重写每一个定义,其中的初始化操作会被剥离。
  2. 类的拷贝构造函数的会被安插进去

其 C++ 伪码可能像下面这样:

void foo_bar() {
	X x1;
	X x2;
	X x3;

	x1.X::X( x0 );
	x2.X::X( x0 );
	x3.X::X( x0 );
}

参数的初始化

C++ 标准说,把一个类对象当做参数传给一个函数(或是作为一个函数的返回值),相当于以下形式的初始化操作:

X xx = arg;

其中 xx 代表形式参数 (或返回值) 而 arg 代表真正的参数值。因此,若已知这个函数:

void foo( X x0 );

下面这样的调用方式:

X xx;
foo( xx );

将会要求局部实体 x0 以逐成员的方式将 xx 当作初值。在编译器实现技术上,有一种策略是导入所谓的暂时性对象(或临时对象),并调用拷贝构造函数将它初始化,然后将该暂时性对象(或临时对象)交给函数。

例如将前一段程序代码转换如下:

X __temp0;
__temp0.X::X( xx );
foo( __temp0 ):

然而这样的转换是有问题的,因为我们又要调用 foo( X x0 ) 再展开下去,产生无穷无尽的调用,因此这种情况,函数声明也相当于改为 void foo( X& x0 )

下面的为vs2022下的汇编代码
可以看到在调用 foo 之前先调用了X的拷贝构造函数

在拷贝构造函数中可以看到对于变量的地址

之后将rax的值传给了rcx,可以看到rax中的值就是之前this指针的地址,然后调用 foo

可以看到 x0 的地址为之前 this 指针的地址,说明确实是 void foo( X& x0 )


返回值的初始化

已知下面这个函数定义

X bar()
{
	X xx;
	return xx;
}

那么 bar() 的返回值如何从局部对象 xx 中拷贝过来,在 cfront 中的解决方法是一个双阶段转化:

  1. 首先加上一个额外参数,类型是类对象的一个引用。这个数将用来放置拷贝构造得到的返回值
  2. 在return指令之前安插一个拷贝构造调用操作,以便将想传回的对象的内容当做上述新增参数的初值

真正的返回值是什么?最后一个转化操作会重新改写函数,使它不传回任何值,bar() 转换如下:

void bar( X& __result )
{
	X xx;
	xx.X::X();
	__result.X::XX( xx );
	
	return;
}

现在编译器必须转换每一个 bar() 调用操作,以反映其新定义。例如:

X xx = bar();

将被转换为下列两个指令句:

X xx;
bar( xx );

将被转换为下列两个指令句:

X xx;
bar( xx );

而对于语句:

bar().memfunc();

可能被转化为:

X __temp0;
( bar( __temp0 ), __temp0 ).memfunc();

同样道理,如果程序声明了一个函数指针,像这样:

X ( *pf )();
pf = bar;

它也必须被转化为:

void ( *pf )( X& );
pf = bar;

在使用者层面做优化

比如下面的函数

X bar (const T &y, const T &z)
{
	X xx;
	xx.m_x = y + z;
	return xx;
}

如果使用者将这个函数改为

X bar( const T &y, const T &z)
{
	return X( y, z );
}

于是当 bar() 的定义被转换之后,效率会比较高:

void bar( X &__result )
{
	__result.X::X( y, z );
	return;
}

__result 被直接计算出来,而不是经由拷贝构造拷贝而得。


在编译器层面做优化

NRV 优化

在一个如 bar() 这样的函数中,所有的 return 指令传回相同的具名数值,比如上面实例中的局部变量 xx,编译器有可能自己做优化,方法是以 result 参数取代 named return value。例如下面的 bar() 定义:

X bar()
{
	X xx;
	// ... 处理 xx
	return xx;
}

编译器把其中的 xx__result 取代:

void bar( X& __result )
{
	__result.X::X();
	// ... 直接处理__result
	return ;
}

这样的编译器优化操作,有时候被称为 Named Return Value(NRV) 优化。

看下 VS2022中,下面的实例

X bar(const int y, const int z)
{
    X xx;
    xx.m_x = y + z;
    return xx;
}

int main()
{
	X xxx = bar(1, 2);
}

可以看到像实参一样将 xxx 的地址进行了压栈

可以看到,这里的 xx 的值实际就是 xxx 的地址,然后调用了默认构造函数,就是相当于xxx.X::X()

VS2022貌似默认就算未有明确定义的拷贝构造函数,也会进行NRV优化,看下面这个case

#include <iostream>

class test {
	friend test foo(double);
public:
	test()
	{
		memset(array, 0, 100 * sizeof(double));
	}
private:
	double array[100];
};

test foo(double val)
{
	test local;

	local.array[0] = val;
	local.array[99] = val;
	return local;
}

int main()
{
	for (int cnt = 0; cnt < 100000000; cnt++)
	{
		test t = foo(double(cnt));
	}
	return 0;
}

这里可以看到 ebp-334h 就等于 &t


foo 函数中的 local 可以看到就是 t 的地址,说明进行了 NRV 优化

NRV优化的弊端

虽然NRV优化提供了重要的效率改善,还是有有一些弊端:

  1. 优化由编译器默默完成,而它是否真的被完成,并不十分清楚(因为很少由编译器会说明其实现程度,或是否实现)。
  2. 一旦函数变得比较复杂,优化也就变得比较难以施行。在 cfront 中,只有当所有的 named return 指令句发生于函数的 top level 时,优化才施行。如果导入 “a nested local block with a return statement”,cfront 就会静静地将优化关闭
  3. NRV 优化可能会带来意想不到的问题

第三点,可以考虑下面这个case,我们在上面的 test 类中,加入一个类静态变量 count,其初始值为 0,加入一个析构函数

	~test()
	{
		count++;
	}

最后输出这个 test::cout,完整代码如下:

#include <iostream>
using namespace std;
class test {
	friend test foo(double);
public:
	static int count;
	test()
	{
		memset(array, 0, 100 * sizeof(double));
	}
	~test()
	{
		count++;
	}
private:
	double array[100];
};

int test::count = 0;

test foo(double val)
{
	test local;

	local.array[0] = val;
	local.array[99] = val;
	return local;
}

int main()
{
	for (int cnt = 0; cnt < 100000000; cnt++)
	{
		test t = foo(double(cnt));
	}
	cout << test::count << endl;
	return 0;
}

正常不被NRV优化,我们期望的应该是 200000000,但实际输出了 100000000

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

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

相关文章

在AndroidStudio创建Flutter项目并运行到模拟器

1.Flutter简介 Flutter是Google开源的构建用户界面&#xff08;UI&#xff09;工具包&#xff0c;帮助开发者通过一套代码库高效构建多平台精美应用&#xff0c;支持移动、Web、桌面和嵌入式平台。Flutter 开源、免费&#xff0c;拥有宽松的开源协议&#xff0c;适合商…

基于模糊PI控制算法的龙格库塔CSTR模型控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于模糊PI控制算法的龙格库塔CSTR模型控制系统simulink建模与仿真。基于模糊PI控制算法的龙格-库塔&#xff08;Runge-Kutta, RK&#xff09;连续搅拌釜反应器&#xff08;Co…

C语言.自定义类型:结构体

自定义类型&#xff1a;结构体 1.结构体类型的声明1.1结构体回顾1.1.1结构体的声明1.1.2结构体变量的创建和初始化 1.2结构体的特殊声明1.3结构体的自引用 2.结构体内存对齐2.1对齐规则2.2为什么存在内存对齐2.3修改默认对齐数 3.结构体传参4.结构体实现位段4.1什么是位段4.2位…

[附源码]SpringBoot+Vue网盘项目_仿某度盘

视频演示 [附源码]SpringBootVue网盘项目_仿某度盘 功能介绍 支持秒传支持视频音频播放、拖拽进度条、倍速播放等支持图片预览&#xff0c;旋转&#xff0c;放大支持多人一起上传&#xff0c;共享上传进度&#xff08;例如a上传苍老师学习资料到50%&#xff0c;突然b也上传苍老…

PHP源码_最新Ai对话系统网站源码 ChatGPT+搭建教程+前后端

基于ChatGPT开发的一个人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过学习和理解人类的语言来进行对话&#xff0c;还能根据聊天的上下文进行互动&#xff0c;真正像人类一样来聊天交流&#xff0c;甚至能完成撰写邮件、视频脚本、文案、翻译、代码&#xff0c;写论…

【MySQL精炼宝库】深度解析索引 | 事务

目录 一、索引 1.1 索引(index)概念&#xff1a; 1.2 索引的作用&#xff1a; 1.3 索引的缺点&#xff1a; 1.4 索引的使用场景&#xff1a; 1.5 索引的使用&#xff1a; 1.6 面试题:索引底层的数据结构&#xff08;核心内容&#xff09;&#xff1a; 1.7 索引列查询(主…

【opencv4.8.1 源码编译】windows10 OpenCV 4.8.1源码编译并实现 CUDA 12加速

Windows 下使用 CMake3.29.2 Visual Studio 2022 编译 OpenCV 4.8.1 及其扩展模块cuda12.0teslaT4显卡 记录自己在编译时踩过的坑&#xff0c;避免下次再犯或者给有需要的人。 在实际使用中&#xff0c;如果是对处理时间要求比较高的场景&#xff0c;使用OpenCV处理图片数据很…

Flask教程2:flask高级视图

文章目录 add_url_rule类视图的引入装饰器的自定义与使用蓝图的使用url_prefix设置蓝图前缀 add_url_rule 欲实现url与视图函数的绑定&#xff0c;除了使用路由装饰器app.route&#xff0c;我们还可以通过add_url_rule(rule,endpointNone,view_funcNone)方法&#xff0c;其中&…

Flutter笔记:Widgets Easier组件库(1)使用各式边框

Flutter笔记 Widgets Easier组件库&#xff08;1&#xff09;&#xff1a;使用边框 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress o…

【stomp 实战】Spring websocket 用户订阅和会话的管理源码分析

通过Spring websocket 用户校验和业务会话绑定我们学会了如何将业务会话绑定到spring websocket会话上。通过这一节&#xff0c;我们来分析一下会话和订阅的实现 用户会话的数据结构 SessionInfo 用户会话 用户会话定义如下&#xff1a; private static final class Sessio…

利用Argo数据分别计算温度、盐度和温盐所造成的比容海平面变化

本文所用到的温盐数据集&#xff1a;IPRC&#xff08;美国夏威夷大学国际太平洋研究中心&#xff09; Argo data products | Argo (ucsd.edu)https://argo.ucsd.edu/data/argo-data-products/ 理论知识&#xff08;相关计算公式&#xff09;&#xff1a; 代码和工具包准备&…

python 中的数据结构

python 中的数据结构 1.1 序列 序列时有索引的数组 举例实现&#xff1a; a["北京","上海","广州","深圳","重庆","成都"] print(a[2]) print(a[-1] " " a[-2]) print(a[1:3]) # 运行结果 "&…

Vulnhub-DIGITALWORLD.LOCAL: VENGEANCE渗透

文章目录 前言1、靶机ip配置2、渗透目标3、渗透概括 开始实战一、信息获取二、smb下载线索三、制作字典四、爆破压缩包密码五、线索分析六、提权&#xff01;&#xff01;&#xff01; Vulnhub靶机&#xff1a;DIGITALWORLD.LOCAL: VENGEANCE ( digitalworld.local: VENGEANCE …

chrome和drive安装包路径

Chrome for Testing availability (googlechromelabs.github.io) 下载Stable下面的包哈

【Leetcode每日一题】 分治 - 排序数组(难度⭐⭐)(69)

1. 题目解析 题目链接&#xff1a;912. 排序数组 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 归并排序&#xff08;Merge Sort&#xff09;是一种采用“分而治之”&#xff08;Divide and Conquer&#xff09;策略…

LLM之RAG实战(三十八)| RAG分块策略之语义分块

在RAG应用中&#xff0c;分块是非常重要的一个环节&#xff0c;常见的分块方法有如下几种&#xff1a; Fixed size chunkingRecursive ChunkingDocument Specific ChunkingSemantic Chunking a&#xff09;Fixed size chunking&#xff1a;这是最常见、最直接的分块方法。我们…

C/C++基础语法练习 - 计算阶乘(新手推荐阅读✨)

题目链接&#xff1a;https://www.starrycoding.com/problem/160 题目描述 给定一个整数 n n n&#xff0c;输出阶乘 n ! n! n!。 输入格式 一个整数 n ( 1 ≤ n ≤ 20 ) n(1 \le n \le 20) n(1≤n≤20)。 输出格式 一个整数 n ! n! n!。 输入样例1 16输出样例1 20922…

树的中心 树形dp

#include<bits/stdc.h> using namespace std; int n; const int N 100005; // 无向边 int ne[N * 2], e[N * 2], idx; int h[N]; int vis[N];int ans 0x7fffffff;void add(int a, int b) {e[idx] b, ne[idx] h[a], h[a] idx; }int dfs(int u) { // 作为根节点vis[u]…

机器学习:基于Sklearn,使用随机森林分类器RandomForestClassifier检测信用卡欺诈

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

分享一份物联网 SAAS 平台架构设计

一、架构图**** 二、Nginx**** 用于做服务的反向代理。 三、网关**** PaaS平台所有服务统一入口&#xff0c;包含token鉴权功能。 四、开放平台**** 对第三方平台开放的服务入口。 五、MQTT**** MQTT用于设备消息通信、内部服务消息通信。 六、Netty**** Socket通信设…