Unintuitive expression evaluation with incrementation
Asked Answered
R

3

46

For the following code

<?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);

I obtained this result:

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

I expected 1,2,3,4,5 rather than 1,3,3,4,5. Why after $a=1; $b=$a+$a++; we obtain $b=3?

PHP 7.1.5-1+deb.sury.org~xenial+1 (cli) (built: May 11 2017 14:07:52) ( NTS )

Rightward answered 5/9, 2017 at 12:11 Comment(10)
weird..its the same result as for $b=$a+++$a++Dessertspoon
I'm not sure there is a correct answer to begin with. Is the order of operand evaluation for + guaranteed? If not, this is simply undefined behaviour.Sternson
Nice question. But I can't figure it out! Why its happening!Dictator
$b = $a++ +$a; is 3 while $b = $a+ ++$a; is 4!! AstonishingHecto
@Thamilan $a++ + $a (anything with the post-increment operator) depends on the undefined order of operations, while $a + ++$a (anything with the pre-increment operator) should be guaranteed to always have the same result.Sternson
@Sternson Thanks! just wondering about the programming flows ;)Hecto
There is great article about this: > gist.github.com/nikic/6699370 Problem is explained by opcode - code generated from PHP for Zend VM.Rightward
This question made for an excelent trick question at work hahaMobley
Possible duplicate of Why is $a + ++$a == 2?Ashok
Great lesson for life: Intuition is always a function of personal vita. Your intuition does never equal other people's intuition.Warga
G
30
$a=1;   $b=$a+$a++;           var_dump($b);            // int(3)

You assumed that the expression above is evaluated from left to right as follows (temporary variables $u and $v are introduced in the explanation for clarity):

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

But there is no guarantee that the subexpressions are evaluated in a specified order. The documentation page of the PHP operators states (the emphasis is mine):

Operator precedence and associativity only determine how expressions are grouped, they do not specify an order of evaluation. PHP does not (in the general case) specify in which order an expression is evaluated and code that assumes a specific order of evaluation should be avoided, because the behavior can change between versions of PHP or depending on the surrounding code.

Only by chance the values computed by PHP for the other expressions match the values you assumed. Their values might be different when the code is executed using a different version of the PHP interpreter.

Germaine answered 5/9, 2017 at 12:43 Comment(9)
I guess this means that in the expression a() + b() there is no guarantee a runs before b?Teasel
And what about something like isset($a) && doStuffWith($a)? Is it possible that doStuffWith($a) will run if $a is in fact not set?Teasel
No. The operands of the logical AND (&&) and logical OR (||) operators are always evaluated from left to right because the evaluation is "short-circuited". This means the evaluation of the expression stops when the evaluated operands determine the final result. This means isset($a) is always evaluated first and if it returns FALSE then doStuffWith($a) is not evaluated because its value cannot change the value of the expression. See "Example #1" on the linked page.Germaine
I'm still curious how this is actually implemented so that "the behavior can change depending on the surrounding code" (as we see in the example with one more addition).Ferne
@Ferne - I don't know how the PHP VM is implemented, but one common optimisation for stack machines is that if you have a sequence of operations that's like a op1 (b op2 c) it compiles to push a; push b; push c; op2; op1; which needs space for 3 values on the stack, but if you instead transform it to (b op2 c) op1r a (where op1r is a variant of op1 that accepts its arguments in the opposite order) that compiles to push b; push c; op2; push a; op1r, which only needs space for 2 values on the stack.Marcello
Note that "$a+$a++" is a close match to this pattern. If PHP uses a stack-based VM, it probably compiles (for a strict left-to-right interpretation) to push $a; push ref($a); push $a; increment; store; add, whereas varying the order to push ref($a); push $a; increment; store; push $a; add decrease the temporary workspace requirements by one value. It's quite possible that doing so is only relevant if the workspace required is within a certain range (perhaps if a certain number of stack items are stored in a faster storage system and the code will overflow that by a single value), ...Marcello
... so when the expression becomes more complex the optimiser doesn't see the need to do it any more.Marcello
A compiler that optimizes the code it generates can identify common sub-expressions in a large expression and generate code that computes the common sub-expressions only once. For example, $c=($a+$b++)+($a+$b++); can be evaluated from left to right as $temp=$a+$b; $b++; $c=$temp+$a+$b; $b++;. If the compiler is able to find common sub-expressions, it can evaluate it as $c=2*($a+$b); $b+=2; (which requires less operations). Both evaluations are allowed in PHP by the lack of language rules regarding the order of evaluation.Germaine
I don't know if the PHP compiler is currently able to optimize the common sub-expression as described in my previous comment.Germaine
S
23

PHP does not (in the general case) specify in which order an expression is evaluated and code that assumes a specific order of evaluation should be avoided [..]

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

The reason you get differing results is that sometimes the right and sometimes the left operand are evaluated first. PHP makes no guarantees about the order of operations, so there is no correct answer and this falls squarely into the category of undefined behaviour.

Sternson answered 5/9, 2017 at 12:23 Comment(2)
Shouldn't parentheses fix the order-of-operations issue? For example, why doesn't $b=($a++) make $b equal to 2?Elaterid
No, parentheses specify grouping, i.e. which expressions will be evaluated against which expressions. Unless those expressions are nested they still don't specify the order of evaluation. I.e. $a + ($a + $a++) guarantees that $a + $a++ will be evaluated first, but it does not guarantee anything about which operand within that will be evaluated first. And $a + ($a++) is the same as $a + (((($a++)))) and will simply be reduced to the equivalent $a + $a++ by the engine; i.e. it doesn't have any influence on single operands.Sternson
D
10

According to PHP manual

Operator precedence and associativity only determine how expressions are grouped, they do not specify an order of evaluation. PHP does not (in the general case) specify in which order an expression is evaluated and code that assumes a specific order of evaluation should be avoided, because the behavior can change between versions of PHP or depending on the surrounding code.

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

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

The weird thing is that I'd have expected the other lines like $b=$a+$a+$a++; follow the same pattern, but it seems not.

Dull answered 5/9, 2017 at 12:25 Comment(1)
For the other operations it entirely depends on how the engine chooses to optimise the code. Something is specifically triggering a different flow for the single-addition-operator case.Sternson

© 2022 - 2024 — McMap. All rights reserved.