为什么C++中的函数参数评估顺序是未指定的?

13
标准没有指定对参数的评估顺序。
参数的评估顺序是未指定的。
“在没有对表达式评估顺序的限制下,可以生成更好的代码”这句话意味着什么?
如果要求所有编译器按照从左到右的顺序评估函数参数,会有什么缺点?由于这个未指定的规范,编译器会进行哪些优化?

4
允许编译器重新排序操作数的计算顺序可以提供更多的优化空间。 - Mysticial
1
@Mysticial:看起来可能很荒谬,但那应该是一个答案,而且实际上是被接受的答案! - David Rodríguez - dribeas
编译器执行哪些优化操作? - unj2
如果我能想到一个例子,我会把它作为答案。 - Mysticial
有些调用约定从右到左传递参数,而另一些则从左到右传递。按照它们被推送的顺序进行评估有时更有效率。 - Raymond Chen
2个回答

29

允许编译器重新排列操作数的评估顺序,为优化提供了更多的空间。

这里是一个完全虚构的示例,仅供说明目的。

假设处理器可以:

  • 每个周期发出1条指令。
  • 在1个周期内执行加法。
  • 在3个周期内执行乘法。
  • 可以同时执行加法和乘法。

现在,假设你有一个函数调用,如下所示:

foo(a += 1, b += 2, c += 3, d *= 10);

如果您在没有乱序执行的处理器上从左到右执行此操作:

Cycle - Operation
0     -    a += 1
1     -    b += 2
2     -    c += 3
3     -    d *= 10
4     -    d *= 10
5     -    d *= 10

如果您允许编译器重新排序它们:(并首先开始乘法)

Cycle - Operation
0     -    d *= 10
1     -    a += 1, d *= 10
2     -    b += 2, d *= 10
3     -    c += 3

所以是6周期对4周期。

再次强调,这完全是虚构的。现代处理器比这复杂得多。但你能理解这个思路。


5
这里有一个简单的例子。假设你有一个函数调用如下所示:
// assume that p is a pointer to an integer
foo(*p * 3, bar(), *p * 3 + 1);

编译器需要对指针 p 进行两次解引用(并根据结果进行一些计算)并调用 bar 一次。如果编译器很聪明,它可能会重新排序求值顺序。
int temp = *p * 3;
foo(temp, bar(), temp + 1);

这样它只需要做一次“解引用,乘以3”的操作。这被称为公共子表达式消除。


3
公平地说,即使是保证求值顺序的语言也可以做到这一点,前提是它们可以“证明”在此期间p的值不会改变。当然,在C内存模型中,这几乎是不可能保证的,这可能就是为什么需要未定义的求值顺序来实现这种优化的原因。 - Joey
如果可以证明的话,优化器可以通过类似规则重新排序评估,即使顺序是由标准规定的。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接