带有增量的不直观的表达式评估

Posted

技术标签:

【中文标题】带有增量的不直观的表达式评估【英文标题】:Unintuitive expression evaluation with incrementation 【发布时间】:2018-02-13 17:34:46 【问题描述】:

如下代码

<?php

$a=1;   $b=$a++;              var_dump($b);
$a=1;   $b=$a+$a++;           var_dump($b);
$a=1;   $b=$a+$a+$a++;        var_dump($b);
$a=1;   $b=$a+$a+$a+$a++;     var_dump($b);
$a=1;   $b=$a+$a+$a+$a+$a++;  var_dump($b);

我得到了这个结果:

int(1)
int(3)
int(3)
int(4)
int(5)

我预计是 1,2,3,4,5 而不是 1,3,3,4,5。为什么在$a=1; $b=$a+$a++;之后得到$b=3

PHP 7.1.5-1+deb.sury.org~xenial+1 (cli)(构建时间:2017 年 5 月 11 日 14:07:52)(NTS)

【问题讨论】:

奇怪..与$b=$a+++$a++的结果相同 我不确定是否有一个正确的答案。 + 的操作数评估顺序是否得到保证?如果不是,这只是未定义的行为 好问题。但我想不通!为什么会这样! @Thamilan $a++ + $a(任何带有后增量运算符)取决于未定义的操作顺序,而应保证$a + ++$a(任何带有前增量运算符)始终具有结果相同。 Why is $a + ++$a == 2?的可能重复 【参考方案1】:
$a=1;   $b=$a+$a++;           var_dump($b);            // int(3)

假设上面的表达式从左到右计算如下(为了清楚起见,在解释中引入了临时变量$u$v):

 $a = 1;
 $u = $a;              //    ($a)   the LHS operand of `+`
 $v = $a;              //  \ ($a++) the RHS operand of `+`
 $a ++;                //  /
 $b = $u + $v;         // 2 (1+1)

但是不能保证子表达式会按照指定的顺序进行计算。 PHP operators 状态的文档页面(重点是我的):

运算符优先级和关联性仅决定表达式的分组方式,它们不指定求值顺序。 PHP 没有(在一般情况下)指定表达式的求值顺序,并且应该避免采用特定求值顺序的代码,因为行为可能会在 PHP 版本之间发生变化,或者取决于周围的代码。

PHP 为其他表达式计算的值仅与您假设的值相匹配。当使用不同版本的 PHP 解释器执行代码时,它们的值可能不同。

【讨论】:

我猜这意味着在表达式a() + b() 中不能保证ab 之前运行? 没有。 logical AND (&amp;&amp;) 和 logical OR (||) 运算符的操作数总是从左到右计算,因为计算是“短路的”。这意味着当计算的操作数确定最终结果时,表达式的计算停止。这意味着isset($a) 总是首先被评估,如果它返回FALSE,那么doStuffWith($a) 不会被评估,因为它的值不能改变表达式的值。请参阅链接页面上的“示例 #1”。 @Bergi - 我不知道 PHP VM 是如何实现的,但堆栈机器的一个常见优化是,如果您有一系列操作,例如 a op1 (b op2 c),它会编译为 push a; push b; push c; op2; op1;它需要堆栈上 3 个值的空间,但是如果您将其转换为 (b op2 c) op1r a(其中 op1rop1 的变体,它以相反的顺序接受其参数)编译为 push b; push c; op2; push a; op1r,仅堆栈上的 2 个值需要空间。 请注意,"$a+$a++" 与此模式非常匹配。如果 PHP 使用基于堆栈的 VM,它可能会编译(对于严格的从左到右的解释)为 push $a; push ref($a); push $a; increment; store; add,而将顺序更改为 push ref($a); push $a; increment; store; push $a; add 会将临时工作空间要求减少一个值。这样做很可能仅在所需的工作空间在一定范围内时才相关(也许如果一定数量的堆栈项存储在更快的存储系统中并且代码将溢出单个值),... ...所以当表达式变得更复杂时,优化器不再需要这样做了。【参考方案2】:

PHP 没有(在一般情况下)指定表达式的求值顺序,并且应避免使用假定特定求值顺序的代码 [..]

http://php.net/manual/en/language.operators.precedence.php

您得到不同结果的原因是,有时首先计算右操作数,有时先计算左操作数。 PHP 不保证操作的顺序,所以没有正确答案,这完全属于undefined behaviour.

【讨论】:

括号不应该解决操作顺序问题吗?例如,为什么$b=($a++) 不使$b 等于2 不,括号指定分组,即哪些表达式将根据哪些表达式进行评估。除非这些表达式是嵌套的,否则它们仍然没有指定评估顺序。 IE。 $a + ($a + $a++) 保证将首先评估 $a + $a++,但它不保证首先评估其中的哪个操作数。并且$a + ($a++)$a + (((($a++)))) 相同,并且会被引擎简单地简化为等效的$a + $a++;即它对单个操作数没有任何影响。【参考方案3】:

根据PHP manual

运算符优先级和关联性仅决定表达式的方式 是分组的,它们没有指定评估顺序。 PHP 没有 (在一般情况下)指定表达式的顺序 评估和假定特定评估顺序的代码应该 应避免,因为行为可能会在 PHP 版本或 取决于周围的代码。

<?php
$a = 1;
echo $a + $a++; // may print either 2 or 3

$i = 1;
$array[$i] = $i++; // may set either index 1 or 2
?>

奇怪的是,我本以为像 $b=$a+$a+$a++; 这样的其他行会遵循相同的模式,但似乎并非如此。

【讨论】:

对于其他操作完全取决于引擎选择如何优化代码。对于单加法运算符的情况,某些东西特别触发了不同的流程。

以上是关于带有增量的不直观的表达式评估的主要内容,如果未能解决你的问题,请参考以下文章

python如何评估“is”表达式? [复制]

如何打破 Javascript 中的不矛盾定律?

如何阻止 EL 表达式在未呈现的 JSF 组件中进行评估?

改进的PID算法

增量算子在字符类型边界上的行为

带有if条件的隐式布尔评估[重复]