如果在函数调用中返回本地对象,则必须至少执行三个步骤:
例如:
x = y + z
如果x
是整数对象。应返回y + z
的副本,然后创建新对象,然后x
的赋值运算符将此对象作为参数。
所以我的问题是:
int
,double
......?答案 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主要是为程序员提供更多控制返回值优化等功能的能力。