探讨“临时对象”(temporary object)

MSDN中对VS2012版本的临时对象的说明以下:express

    在某些状况下,编译器有必要产生临时对象。数组

  1.     当初始化一个 常量引用 const reference)时,若是给定的初始化对象类型与目标引用类型不一样(可是二者  可以相互转换),须要产生临时对象;
  2.     当函数的返回值是用户自定义类型,且程序中未将此返回值拷贝到其余对象中时,须要产生临时对象;
  3.     当给定的对象显式向自定义对象类型转换时,产生临时对象;

IBM官网上的给出的描述以下:数据结构

    C++中编译器有些时候有必要产生临时对象。一般在初始化引用、计算(评估evaluation)含有标砖类型转换的表达式、参数传递、函数返回、评估异常抛出表达式(throw expression)。jsp

参考资料:函数

http://msdn.microsoft.com/en-us/library/a8kfxa78(v=vs.110).aspx 学习

http://publib.boulder.ibm.com/infocenter/comphelp/v7v91/index.jsp?topic=%2Fcom.ibm.vacpp7a.doc%2Flanguage%2Fref%2Fclrc03cplr116.htm 测试

    总之,读完以后,是否是还感受临时对象捉摸不定呢?的确,C++标准中没有明确给出临时对象的产生规则和条件,由编译器自动产生的,不论是处于效率仍是其余缘由,各编译器之间的产生时机和方式都略有不一样,下面就MSVC编译器进行一些基本的探讨,下面的代码都是在VS2008环境下编译经过的。优化

状况一:lua

    经过不一样设数据类型来初始化常量引用

spa

    在(1)代码处,设置断点,进行调试,查看反汇编以下:


    其中fild dword ptr[ebp-8] 是将整型变量iNum转换成浮点型(float)并压栈(至关于复制了一份iNum的数据存储到浮点寄存器中)。【有关浮点数的汇编指令,可参见百度百科相关说明】。而后fstp dwrod ptr[ebp-20h]是出栈指令,将刚才存储的浮点数据转存到ebp-20h栈空间中。也就是说ebp-20h处就是咱们要找的“临时变量”。经过对比const float& r10=fNum;一行的反汇编指令,结果就更明显了。

状况二:当函数返回值是自定义类型时。

咱们知道,一般函数的返回值有两种方式来传递:寄存器和栈。内置数据类型,一般都是经过寄存器(MSVC下是EAX)来直接做为返回值的存储容器,将结果由被调用函数传递给调用者。可是若是返回值是字符串、数组等比较大的数据结构时,一个32位的寄存器EAX是不够的,此时经常会利用到其余的寄存器(例如ECXEDX)等来进行转存容器。自定义的类对象,一般经过在栈中开辟一块空间(大小由编译器根据类对象的大小自动设定)来转存返回值。因此能够简单的认为在栈中开辟的转存空间就是一份返回值的副本,即“临时对象”。经过寄存器的转存,咱们不能说是“临时对象”,由于CPU的一切数据操做都是经过各类寄存器来完成的,何况寄存器中的数据也不存在声明周期问题,随时有可能被覆盖掉,固然若是进行了进栈操做,那就能够叫作临时对象了。

示例代码以下:

main()函数中测试代码:

                                 
图一

Point是自定义的一个二维平面点类(由于若是直接定义成简单的POD类,编译器会直接将类进行优化,当作两个单独的整型数据成员来看,因此在定义类时,添加了本身的构造函数、析构函数、operator +、取值和设定函数)。类结构以下:


                             图二

程序的直接运行(CTRL+F5)结果以下:


由输出结果能够直接看出,构造函数与析构函数不对称,其中0012FE54处的对象只调用了析构函数,可是没有调用构造函数。因此能够怀疑0012FE54应该是一个“temporary object”。(可是为何能够不调用构造函数,却要调用析构函数呢?这一点我还没搞明白,有待考究一下)。

下面单步调试进入到函数体中,看看究竟:

首先进入到(图一)中的(1)处:

其反汇编代码以下:

其中,004111C2是构造函数Point(int x, int y)在跳转表中的位置,以下:


如图所示,再次跳转到Pointint x, int y)构造函数00411720处。

Pointint x, int y)构造函数反汇编代码以下:

能够用下面的示意图来表示有参构造函数的操做过程:

一样,运行到(图一)中(2)时,结果彻底相同:

示意图以下:


而后到(图一)中的(3)处:

此时调用的是无参构造函数,其反汇编代码以下:

操做过程示意图以下;


最后,到了咱们此部分的重点,p3=p1+p2;

其反汇编代码以下:


能够看得出来,在调用operator+00411113)函数时,压入了三个参数进栈:分别是p1的地址、p2的地址,和0x0012FF54(——咱们所找的临时对象)。下面就进入到了oeprator+友元函数体中,其反汇编代码以下:

从反汇编代码能够看出,在return p3;语句时,将局部对象p30x0012FE10)拷贝到临时对象0x0012FE54(进入函数前压入栈中的第三个参数——咱们要找的临时对象)中。

拷贝完成后,局部变量p3(0x0012FE10)调用其析构函数"call 00411037"。此时,因为p3=p1+p2求值表达式(evaluation expression)还未完成,因此临时对象(0012FE54)并未调用其析构函数。

接着往下看。


而后将临时对象中的值拷贝到了主函数的局部对象p3中(如图中红色代码所示)。至此,求值表达式运算完成,意味着临时对象(0x0012FE54)的生存已失去意义,遂将0x0012FE54复制到ECX中压栈,而后调用析构函数。

函数最后:return 0;时,主函数中的p1,p2,p3生命周期也就结束了,因此按照与构造函数相反的顺序依次调用其析构函数,反汇编代码以下:


参考书籍:

《深度探索C++对象模型》

C++反汇编与逆向分析技术揭秘》
注:文中是我的学习过程当中的笔记,欢迎你们批评指正,交流沟通才能进步。

我的邮箱:zssure@163.com

相关文章
相关标签/搜索