自己动手理解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作为外部变量的地址。

posted on 2010-08-20 22:56  烛秋  阅读(2059)  评论(0编辑  收藏  举报