关于NRV优化详细分析
来源:互联网 发布:淘宝直通车黑车技术 编辑:程序博客网 时间:2024/05/16 14:17
在C++中,函数返回整数或指针是通过eax寄存器进行传递的,理解起来比较简单。
但是返回对象或结构体一直是令人感到困惑的问题。今天我整理了一下,将整个返回过程写下来,以作备用。
还是先通过一个例子来理解这个问题:
首先,定义一个类Vector:
class Vector{public: int x,y;};
Vector add(Vector& a, Vector & b){ Vector v; v.x = a.x + b.x; v.y = a.y + b.y; return v;}
现在的问题是:
如果调用如下语句:
Vector a, b;Vector c = add(a, b); (调用拷贝构造函数,而不是赋值语句)
请问从a, b传入函数开始,一共创建了多少个对象?
在通常情况下我们会做出如下分析:
1. 在add()函数中创建对象v。
2. 函数返回,创建一个临时变量__temp0,并将v的值拷贝到__temp0中。
3. 最后创建对象c,通过操作符=,将__temp0中的对象拷贝到c中。
但其实,我们会在后面看到,整个过程就只创建了1个对象:c。
为了更清晰的分析整个调用过程,我们为Vector加上默认构造函数和拷贝构造函数,并增加一个静态变量count用于统计构造函数调用次数:
class Vector{public: static int count; static void init() { count = 0; } int x,y; Vector() { x = 0; y = 0; //For analysis. count++; printf("DefaultConstructor was called.[0x%08x]\n",this); } Vector(const Vector& ref) { x = ref.x; y = ref.y; //For analysis. count++; printf("CopyConstructor was called.[copy from 0x%08x to 0x%08x].\n",&ref, this); } }; int Vector::count = 0;
然后在main()函数中写上调用代码:
Vector a, b;Vector::init();printf("\n--Test add() --\n");Vector c = add(a, b);printf("----Constructors were called %d times. ----\n\n\n",Vector::count);
使用cl编译。
(注:Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86, Microsoft (R) Incremental Linker Version 10.00.40219.01)
完成后,运行程序,得到如下结果:
-- Test add() --Default Constructor was called.[0x0012fef8]Copy Constructor was called.[copy from 0x0012fef8 to 0x0012ff60].---- Constructors were called 2 times. ----
由此可知,在没有优化的情况下,整个调用过程共创建了两个对象:
即:c和__temp0.
整个调用过程伪代码如下:
首先add()函数被编译器看做:
void add(Vector& __result, Vector& a, Vector & b){ __result.x = a.x + b.x; __result.y = a.y + b.y; return;}而调用代码同时被修改为
Vector a, b;Vector::init();printf("\n--Test add() --\n");Vector __temp0; //构造函数.add(__temp0,a, b);Vector c(__temp0); //拷贝构造函数.printf("----Constructors were called %d times. ----\n\n\n", Vector::count);
现在就可以理解输出结果了吧。
这里要强调一点,看到”=”并不等于调用了Operator=()的代码,以下三种情况其实是等效的,都只调用了拷贝构造函数:
Vector b(a);Vector b = a;Vector b = Vector(a);
最精彩的部分在于,如果你用
cl/Ox
编译代码,使优化达到最大,再次运行,得到如下结果:
-- Test add() --Default Constructor was called.[0x0012ff74]---- Constructors were called 1 times. ----
这次,只调用了默认构造函数。这样的修改被称作Named Return Value(NRV) Optimization。
什么是NRV优化呢,顾名思义,就是保存返回值的变量不再使用没名没姓的__temp0这样的东西了,而是直接把c作为返回变量,因此应该将NRV翻译为“有名字的返回变量”吧,侯捷翻译的《深入探索C++对象模型》居然把它称为“具名数值”,真是不知所云。
言归正传,NVR优化的伪代码如下:
Vector c;add(c,a, b);NVR优化的最大好处就是不会再去调用那次多余拷贝构造函数了(把__temp0拷贝到c),因此《深入探索C++对象模型》67页最下面才会说第一版没有拷贝构造函数,所以不能进行优化。其实是指优化的意义不大,或者说没有什么可优化的。
// segment of function: Vector add(Vecotr &a, Vector &b)0040117B mov edx,dword ptr [esp+1Ch] //edx = b.x0040117F add edx,dword ptr [esp+14h] //edx = edx + a.x00401183 mov ecx,dword ptr [esp+20h] //ecx = b.y00401187 add ecx,dword ptr [esp+18h] //ecx = ecx + a.y0040118B mov dword ptr [esp+24h],edx //c.x = edx0040118F mov edx,dword ptr ds:[40BDC0h] //inline - printf arguments00401195 push edx //still arguments00401196 push 4081E8h //arguments0040119B mov dword ptr [esp+30h],ecx //c.y = ecx0040119F call 004011AE //call printf()004011A4 add esp,10h //function return routine.004011A7 xor eax,eax 004011A9 pop esi 004011AA add esp,28h 004011AD ret
(注意:我尝试把Vector的拷贝构造函数删掉,同样生成了上面这段代码(一个字节都没变),因此我推测,拷贝构造函数并不是触发NRV优化的条件了,Lippman的书可能有点过时了。)
但是这样带来的坏处是,如果你在拷贝构造函数里面放上与拷贝无关的代码,比如我放入的printf和count++,那么这些东西就不会被调用了,产生优化前后代码不一致问题。所以大家要在此注意一下。
- 关于NRV优化详细分析
- 关于NRV优化
- nrv优化
- nrv优化
- NRV优化
- 自己动手理解NRV优化
- 理解NRV优化
- [C++] NRV优化
- 编译器的NRV优化
- 自己动手理解NRV优化
- NRV优化详解
- C++拷贝构造函数、无名临时对象以及NRV优化分析
- NRV
- 关于Named Return Value(NRV)optimization-返回值优化
- 【转】关于RVO与NRV
- NRV优化所带来的困惑
- 快速检验NRV优化测试代码
- 快速检验NRV优化测试代码
- java数据类型分类
- Struts2实现文件上传和下载
- 67. Add Binary
- 注册Tomcat为系统服务
- 求在IOS上扫描PDF417的免费插件
- 关于NRV优化详细分析
- ExploitExercises_Nebula_Level12
- Spring框架(四)AOP之代理模式
- Docker部署 elasticsearch 2.X 简要步骤概述
- java的动态代理机制详解
- Ajax post请求跳转页面
- 获取屏幕的宽高
- Google Map Api 谷歌地图接口整理
- 提高Unity中C#代码质量的22条准则