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 )

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Daniel
  • 7,684
  • 7
  • 52
  • 76
  • 4
    weird..its the same result as for `$b=$a+++$a++` – B001ᛦ Sep 05 '17 at 12:15
  • 5
    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*. – deceze Sep 05 '17 at 12:16
  • 4
    Nice question. But I can't figure it out! Why its happening! – JaTurna Sep 05 '17 at 12:19
  • 2
    `$b = $a++ +$a;` is `3` while `$b = $a+ ++$a;` is `4`!! _Astonishing_ – Thamilhan Sep 05 '17 at 12:23
  • 3
    @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. – deceze Sep 05 '17 at 12:25
  • @deceze Thanks! just wondering about the programming flows ;) – Thamilhan Sep 05 '17 at 12:27
  • There is great article about this: > https://gist.github.com/nikic/6699370 Problem is explained by opcode - code generated from PHP for Zend VM. – Daniel Sep 05 '17 at 12:40
  • This question made for an excelent trick question at work haha – Martijn Sep 05 '17 at 13:39
  • 4
    Possible duplicate of [Why is $a + ++$a == 2?](https://stackoverflow.com/questions/9709818/why-is-a-a-2) – bishop Sep 05 '17 at 15:27
  • Great lesson for life: Intuition is always a function of personal vita. Your intuition does never equal other people's intuition. – Sebastian Mach Sep 06 '17 at 09:34

3 Answers3

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.

axiac
  • 68,258
  • 9
  • 99
  • 134
  • 1
    I guess this means that in the expression `a() + b()` there is no guarantee `a` runs before `b`? – Suppen Sep 05 '17 at 15:19
  • And what about something like `isset($a) && doStuffWith($a)`? Is it possible that `doStuffWith($a)` will run if `$a` is in fact not set? – Suppen Sep 05 '17 at 15:38
  • 5
    No. The operands of the [logical AND (`&&`)](http://php.net/manual/en/language.operators.logical.php) and [logical OR (`||`)](http://php.net/manual/en/language.operators.logical.php) 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. – axiac Sep 05 '17 at 15:43
  • 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). – Bergi Sep 05 '17 at 21:33
  • 3
    @Bergi - 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. – Jules Sep 06 '17 at 03:20
  • 1
    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), ... – Jules Sep 06 '17 at 03:30
  • 1
    ... so when the expression becomes more complex the optimiser doesn't see the need to do it any more. – Jules Sep 06 '17 at 03:31
  • 1
    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. – axiac Sep 06 '17 at 06:05
  • I don't know if the PHP compiler is currently able to optimize the common sub-expression as described in my previous comment. – axiac Sep 06 '17 at 06:12
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.

Community
  • 1
  • 1
deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    Shouldn't parentheses fix the order-of-operations issue? For example, why doesn't `$b=($a++)` make `$b` equal to `2`? – mopo922 Sep 05 '17 at 12:35
  • 3
    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. – deceze Sep 05 '17 at 12:39
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.

Salketer
  • 14,263
  • 2
  • 30
  • 58
  • 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. – deceze Sep 05 '17 at 12:26