定义的参数求值顺序导致次优代码? P>这是一个已知的事实,C和C++中的参数求值顺序没有定义: 例如:foo(a(),b())在上面的调用中,由编译器的实现决定选择哪个求值顺序,从而决定首先执行哪个函数。最近,我的一个朋友问为什么C或C++中的评价顺序不明确。当我在谷歌上搜索时,我知道指定求值顺序会导致次优代码生成。但怎么会这样呢?为什么定义的参数求值顺序会导致次优代码?当我提到Java的参数求值顺序时。我在规范中发现了以下内容

定义的参数求值顺序导致次优代码? P>这是一个已知的事实,C和C++中的参数求值顺序没有定义: 例如:foo(a(),b())在上面的调用中,由编译器的实现决定选择哪个求值顺序,从而决定首先执行哪个函数。最近,我的一个朋友问为什么C或C++中的评价顺序不明确。当我在谷歌上搜索时,我知道指定求值顺序会导致次优代码生成。但怎么会这样呢?为什么定义的参数求值顺序会导致次优代码?当我提到Java的参数求值顺序时。我在规范中发现了以下内容,java,c++,c,evaluation,Java,C++,C,Evaluation,15.7.4。参数列表从左到右求值 在方法或构造函数调用或类实例创建表达式中, 参数表达式可能出现在括号内,用逗号分隔。每个论点 表达式似乎在任何参数表达式的任何部分之前进行了完全求值 是的。 如果参数表达式的计算突然完成,则任何参数的任何部分 右侧的表达式是否已计算 这样,java有一个定义的参数评估顺序,但是如果指定这样的行为看起来有点奇怪,说C或C++编译器会产生次优代码。你能解释一下吗?这部分是历史性的:例如,在寄存器很少的处理器上 例如,一种传统的(简单的)优化技术是 首先计算需要最多

15.7.4。参数列表从左到右求值

在方法或构造函数调用或类实例创建表达式中, 参数表达式可能出现在括号内,用逗号分隔。每个论点 表达式似乎在任何参数表达式的任何部分之前进行了完全求值 是的。 如果参数表达式的计算突然完成,则任何参数的任何部分 右侧的表达式是否已计算


这样,java有一个定义的参数评估顺序,但是如果指定这样的行为看起来有点奇怪,说C或C++编译器会产生次优代码。你能解释一下吗?

这部分是历史性的:例如,在寄存器很少的处理器上 例如,一种传统的(简单的)优化技术是 首先计算需要最多寄存器的子表达式。如果有 子表达式需要5个寄存器,其他4个,例如 可以将需要5的结果保存在不需要的寄存器中 由一个需要4

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

至少,这是我的直觉。至少有一个人告诉过我 谁真正在优化器上工作,这将产生重大影响 不同,所以我不会说我对此有把握

编辑:

只是添加一些关于Java模型的注释。当Java是 在设计时,它被设计成一种解释性语言。极端 表现不是问题;目标是极度安全,并且 可复制性。因此,它非常精确地规定了许多事情 任何编译的程序都会有完全相同的行为 不管平台是什么。应该没有未定义的 行为,没有实现定义的行为,也没有未指定的行为 行为。不考虑成本(但相信这是可能的 在任何最广泛使用的机器上以合理的成本完成)。一个 C(以及间接的C++)的最初设计目标是不必要的 额外的运行时成本应该是最低的,以保证平台之间的一致性 这不是一个目标(因为当时,即使是通用平台也各不相同 这种安全虽然令人担忧,但并不是最原始的。虽然 态度已经演变了一些,仍然有一个目标能够实现 有效地支持可能存在的任何机器。没有 需要最新、最复杂的编译器技术。不同的
目标自然会导致不同的解决方案。

Java提出了一种基于堆栈的虚拟机,在这种虚拟机中,对操作数重新排序没有任何好处。根据James Kanze的回答,C语言和大多数完全编译的语言都假定了寄存器体系结构,在这种体系结构中,寄存器“溢出”到内存的代价很高,而且非常容易避免,因此最好对操作数进行重新排序,事实上,可以做各种事情,最大化寄存器使用和最小化溢出。

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

此外,您似乎认为只有两个表达式可以计算参数,但有四个表达式:
a
b
a()
b()
。函数调用的总偏序是从左到右

    a -- a()
             \
         f --- f(a(), b())
             /
    b -- b()
从图中可以看出,存在许多潜在的并行性,而现代编译器可以通过没有指定的求值顺序来获得一些好处

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


另一方面,如果它们是其他表达式,例如宏计算,则如果有增益,这些表达式可能(也可能)会交错。

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

从人的角度来看,逻辑方法是从左到右计算参数(就像Java那样)。从编译器的角度来看,简单的方法是从右到左进行参数求值。这就是说,一旦一个论点被评估,