从函数返回内置类型变量

时间:2011-02-20 06:20:53

标签: c++

如果在函数调用中返回本地对象,则必须至少执行三个步骤:

  1. 调用复制构造函数来保存副本。
  2. 销毁本地对象。
  3. 返回副本。
  4. 例如:

    x = y + z
    

    如果x是整数对象。应返回y + z的副本,然后创建新对象,然后x的赋值运算符将此对象作为参数。

    所以我的问题是:

    • 用于内置类型的流程是否相同,例如intdouble ......?
    • 如果它们不相同,它是如何完成的?

4 个答案:

答案 0 :(得分:3)

语言规范没有说明内置类型和内置运算符的“如何完成”。该语言简单地说内置类型的二进制+的结果是rvalue - 操作数值的总和。而已。没有逐步描述使用内置运算符时会发生什么(有&&,等常见情况。)。

您可以逐步描述重载运算符的工作原理(这是您在问题中所具有的)的原因是因为评估重载运算符的过程来自几个序列点< / em>的。 C ++程序中的序列点实现了离散时间的概念:它是将之前发生的事情与发生的事情分开的唯一事物。后的 。没有分离序列点,就没有“之前”而没有“之后”。

如果操作符过载,则评估过程中会涉及很多序列点,这就是为什么您可以将此过程描述为一系列步骤的原因。内置运算符+的评估过程中没有序列点,所以绝对没有办法逐步描述那里发生的事情。从语言的角度来看,内置的+是通过模糊的不可分割的混合操作来评估的,这些操作会产生正确的结果。

这样做是为了在评估内置运算符时为编译器提供更好的优化机会。

答案 1 :(得分:1)

这取决于几个因素,尤其是编译器的优化级别或容量。在某种程度上,这也取决于调用约定。

所有内置类型都可以放在寄存器上(“异常大”的内置类型除外,例如“long long int”)。基本上,对于所有调用约定,如果返回类型可以放在EAX寄存器上,那么它就是被调用者放置并由调用者检索的位置。那么,这就是你问题的答案。

对于较大的对象,您描述的过程原则上只是真实的,但是这整个copy-destroy-temporary-copy-destroy事物的效率非常低,并且它是任何编译器优化算法的最高优先级。由于物体太大而无法放在寄存器上。它们通常放在堆栈上并留在那里由调用者检索。因为很多时候,它们只是被存储回另一个局部变量,编译器会尝试将这些堆栈槽合并在一起,并且通常被调用函数中的局部变量也将位于同一个槽中,因此,最后,你没有副本,没有破坏,没有临时,没有开销...这是理想的“优化”情况,但编译器并不总是能够实现这一点,而且它还要求对象属于POD类。

答案 2 :(得分:1)

如果你刚才谈到你现在的例子(x = y + z),那么就没有函数调用 - 加法发生在寄存器中。

如果你实际上正在调用一个函数(例如x = sum(y, z)),那么你可以根据调用约定和数据类型获得一些不同的行为(不适合单个寄存器的类型得到特殊处理),但是对于int s,假设它们最终会在EAX寄存器中传回,这是非常安全的。

不涉及构造函数/析构函数!

有关各个数据类型的更多信息,请查看this page上的“返回值”部分 - 我认为这些是针对 cdecl 调用约定的,但它们应该非常普遍。

有关一般调用约定的更多信息,Wikipedia (x86 calling conventions)执行相当彻底的工作。您将对 cdecl (标准C函数)和 thiscall (特别是C ++类成员函数)感兴趣。

希望这有帮助!

答案 3 :(得分:1)

这里有两个问题。首先,关于从函数返回用户定义类型需要做什么的列表基本上是正确的;除了所有实际编译器使用return value optimization以避免临时副本。

另一个问题是“内置类型怎么样?”从概念上讲,同样的事情发生了,只是(1)内置类型具有“普通构造函数”和“平凡的析构函数”(即,编译器知道不需要实际调用任何函数来构造/破坏这些类型),(2)编译器对内置类型的操作比对用户定义类型的操作更了解,并且实际上不需要调用函数,比如添加两个int(而编译器只会使用相关的程序集)代码指令,以及(3)编译器比用户定义的类型更了解内置类型,并且可以更频繁地使用返回值优化。

对于记录,rvalue references and related changes to C++-0x主要是为程序员提供更多控制返回值优化等功能的能力。