定义的参数评估顺序会导致次优代码?

时间:2012-07-12 10:40:53

标签: java c++ c evaluation

众所周知,c和c ++中的参数评估顺序未定义: 例如:foo(a(),b())在上面的调用中,由编译器的实现来决定选择哪个评估顺序,从而决定首先执行哪个函数。最近我的一位朋友问为什么在C或C ++中没有指定评估顺序。当我用Google搜索时,我发现指定评估顺序会导致代码生成次优。但是怎么回事?为什么定义的参数评估顺序会导致次优代码?当我提到Java的论证评估顺序时。我在规范中找到了以下内容。

  

15.7.4。参数列表从左到右评估

     

在方法或构造函数调用或类实例创建表达式中,       参数表达式可能出现在括号内,用逗号分隔。每个论点       表达式似乎在任何参数表达式的任何部分之前被完全评估       这是正确的。       如果参数表达式的求值突然完成,则不会出现任何参数的任何部分   右边的表达似乎已被评估过了?

在这种情况下,Java有一个已定义的参数评估顺序,但如果指定了这样的行为,那么说C或C ++编译器会产生次优代码似乎有点奇怪。你能否对此有所了解?

4 个答案:

答案 0 :(得分:8)

部分历史:在具有少量寄存器的处理器上 例如,一种传统的(和简单的)优化技术是 评估首先需要最多寄存器的子表达式。如果一个 子表达式需要5个寄存器,而另外4个寄存器,例如,你 可以保存不需要的寄存器中需要5的结果 由需要4的人。

这可能与通常认为不太相关。编译器可以 如果表达式没有副作用,则重新排序(即使在Java中) 重新排序不会改变程序的可观察行为。 现代编译器能够比编译器更好地确定这一点 二十多年前(制定C ++规则时)。和 据推测,当他们无法确定这一点时,你就做得足够了 在每个表达中,额外的溢出到内存无关紧要。

至少,这是我的直觉。我被至少一个人告知过 实际上谁在优化工作,它将成为一个重要的 差异,所以我不会说我很确定。

编辑:

只是添加一些关于Java模型的注释。当Java是 正在设计中,它被设计为一种解释语言。极端 表现不是问题;目标是极端安全,并且 可重复性。因此,它非常精确地指定了很多东西,所以 任何编译的程序都会有完全相同的行为 无论平台如何。应该没有未定义的 行为,没有实现定义的行为,也没有未指定 行为。无论成本如何(但相信这可能是成本 在任何最广泛的机器上以合理的成本完成)。一 C(和间接C ++)的初始设计目标是不必要的 额外的运行时成本应该是最小的,平台之间的一致性 不是一个目标(因为有时,甚至普通平台也各不相同 这个安全虽然令人担忧,但并不是原始的。而 态度已经发展了一些,仍然有一个目标能够 有效地支持任何可能在那里的机器。没有 需要最新,最复杂的编译器技术。与众不同 目标自然导致不同的解决方案。

答案 1 :(得分:3)

Java提出了一个基于堆栈的虚拟机,其中重新排序操作数没有任何优势。根据James Kanze的回答,C和大多数完全编译的语言都提出了一种寄存器架构,其中寄存器“溢出”到内存中是昂贵的并且非常值得避免,所以最好对操作数进行重新排序,实际上要做各种各样的操作。事情,最大化寄存器使用并尽量减少溢出。

答案 2 :(得分:1)

我认为我们过分分析了它。真正的答案是可能,在C标准之前的旧日里,当K& R是事实上的标准时,没有人费心去指定评估参数的顺序,不同的编译器实现以不同的方式进行。

从人的角度来看,逻辑方式是从左到右评估参数(就像Java那样)。从编译器的角度来看, easy 方式是从右到左进行参数评估。这样,一旦评估了一个参数,它就不需要保存在任何地方,它可以被推送到堆栈中以备呼叫。大多数使用堆栈作为参数的C实现需要以相反的顺序推送它们。这是因为如果K& RC没有在同一个源文件中定义函数并且程序员利用它来提供原始形式的可变函数,那么K& RC无法计算出函数需要多少个参数。 / p>

因此,标准作家面临着选择这样做的权利"对"方式(从左到右)可能会破坏大量代码或按照大多数现有编译器的方式进行,并可能破坏其他代码或坚持现状并让编译器设计者选择做什么。

无论如何,这是我的意见,不是基于任何事实。

答案 3 :(得分:0)

不适用于您的示例的函数求值,但对于简单表达式,这两个表达式甚至可以并行执行。现代架构是流水线的,并且可以更有效地同时(几乎)同时馈送两条管道,使得必须执行的操作重叠。

此外,您似乎认为只有两个表达式可以为参数进行评估,但有四个:aba()和{{1} }。函数调用的部分顺序是从左到右

b()

从图中可以看出,存在很多潜在的并行性,现代编译器可以通过没有规定的评估顺序获得一些东西。

编辑:根据讨论的一些其他细节。如果 a -- a() \ f --- f(a(), b()) / b -- b() a()是函数调用,则标准保证这些函数调用不会交错,即使函数是内联的。那么上面的图片应该有一个附加约束,b()a()必须以某种方式排序。 (我不知道如何把它放在图片中。)

如果另一方面它们是其他表达式,例如宏观评估,如果有增益,这些表达式可能(并且可能会)交错。