自己动手理解NRV优化
2010.6.29
烛秋
2010.8.20整理
说明:本文整理自:http://blog.csdn.net/wuxupeng999/archive/2010/06/29/5701513.aspx
一、NRV的简单理解
NRV是Named Return Value的简称。NRV优化简单的说:有一条语句,A a = f();其中f()是一个函数,函数里边申请了一个A的对象b,然后把对象b返回。在对象返回的时候,一般情况下要调用拷贝函数,把函数f()里边的局部对象b拷贝到函数外部的对象a。但是如果用了NRV优化,那就不必要调用拷贝构造函数,编译器可以这样做,把a的地址传递进函数f(),然后不让f()申请要返回的对象b的空间,用a的地址来代替b的地址,这样当要返回对象b的时候,就不必要拷贝了,因为b就是a,省去了局部变量b,省去了拷贝的过程。
二、动手测试
书上得来终觉浅。动手测试才是王道。
测试代码如下:
///////////////////////////////////////////////// #include <iostream> #include <cstring> using namespace std; class A { public: A() { cout<<"construct"<<endl; strcpy(name,"zhangsan"); } ~A() { cout<<"destruct"<<endl; } A(const A& temp) { cout<<"copy"<<endl; strcpy(name,temp.name); } private: char name[16]; }; //////////////////////////// A f() { cout<<"------------function f----------"<<endl; A a; return a; } //////////////////////////// int main() { cout<<"------------define-------------"<<endl; A b = f();//A b(f()); return 0; } /* GCC编译: 运行结果: ------------define------------- ------------function f---------- construct destruct */ //////////////////////////// //////////////////////////// /* VS2005 debug下输出: ------------define------------- ------------function f---------- construct copy destruct destruct */ //////////////////////////// /*VS2005 release下输出: ------------define------------- ------------function f---------- construct destruct */ /*G++以及vs2005的release,这就是NRV之后的结果*/
测试环境:32位机器,vs2005编译器。
测试部分截图
图 1 debug模式下main()函数
图 2 debug模式下f()函数
图 3 release模式下 main()函数
图 4 release模式下f()函数
三、分析
(1)debug模式
分析图 1和图 2可以发现在调用f()函数时,传进了一个参数,这个参数就是拷贝构造函数的目标对象。而拷贝构造函数的源对象就是在f()函数里边定义的局部对象。构造对象的过程是这样的:首先在main()函数中申请对象b的空间,注意此时没有构造该对象,仅有地址而已;然后进入f()函数,构造f()函数中的对象a,接着调用拷贝构造函数构造main()函数中的对象b,地址是在main()函数时申请的;结束。
(2)release模式
分析图 3和图 4可以发现在调用f()函数时,通过esi传递了外部对象b的地址,在f()函数里边没有申请局部对象的空间,没调用构造函数(内联了),只是输出"construct",也没调用strcpy函数,而是通过寄存器把"zhangsan"拷贝到esi所指向的地址。release模式做了很大的优化!构造对象的过程是这样的:首先在main()函数申请对象b的空间,注意此时仅有地址而已;然后进入f()函数,构造对象b;结束。
(3)总结
可以这样理解:debug模式下,给函数传递外部变量b的地址,函数内先申请一个局部变量a,接着对a操作,最后调用拷贝构造函数把a拷贝到外部变量b,结束返回。release模式下,给函数传递外部变量b的地址,函数内不申请局部变量a的空间,把b作为a的空间,接着对a操作,结束返回。
release模式下,局部变量构造函数的操作还是有的,就是没有申请空间,通过寄存器拷贝字符串"zhangsan"的过程就是在调用构造函数,不过看起来被内联在f()函数里边了。可以在f()函数返回之前,局部变量定义之后增加几行代码,再进行测试。
四、关于测试
1、 如何测试?为了方便测试可以输出一些字符,然后在OD里使用超级字符串查找插件,方便定位。
2、如何知道有没有申请空间?看函数入口处有没有类似于sub esp,XX的指令,在debug模式下会发现f()函数里边有申请局部变量,而release模式下f()函数没有申请局部变量。
3、一般情况下,堆栈的局部变量会采用[ebp-XX]的方式访问,而通过堆栈传递的参数会采用[ebp+XX]的方式访问。另外要注意在函数采用寄存器传递参数,例如:this指针采用ecx寄存器,esi作为外部变量的地址。