PHP operator precedence "Undefined order of evaluation"? - php

http://www.php.net/manual/en/language.operators.precedence.php#example-115
<?php
$a = 1;
echo $a + $a++; // may print either 2 or 3
?>
The example from the php manual doesn't explain very well. Why isn't $a++ evaluated to 2, and then added to 1, so that it always becomes echo 1 + 2 // equals 3? I don't understand how it "may print either 2 or 3". I thought incremental ++ has "higher precedence" than addition +?
In other words, I don't understand why isn't it...
$a = 1;
1) echo $a + $a++;
2) echo 1 + ($a = 1 + 1);
3) echo 1 + (2);
4) echo 3;

It can be either 2 or 3. However in most of the time it will be 3. So why it MIGHT be 2? Because PHP is not describing in which order expressions are evaluated, since it might depends on the PHP version.

Operator precedence in PHP is a mess, and it's liable to change between versions. For that reason, it's always best to use parentheses to group your in-line equations so that there is no ambiguity in their execution.
The example I usually give when asked this question is to ask in turn what the answer to this equation would be:
$a = 2;
$b = 4;
$c = 6;
$val = $a++ + ++$b - 0 - $c - -++$a;
echo $val;
:)
Depending where I run it now, I get anything between 4 and 7, or a parser error.
This will load $a (1) into memory, then load it into memory again and increment it (1 + 1), then it will add the two together, giving you 3:
$a = 1;
$val = $a + ($a++);
This, however, is a parser error:
$a = 1;
$val = ($a + $a)++;
Anyway, long story short, your example 2) is the way that most versions will interpret it unless you add parenthesis around ($a++) as in the example above, which will make it run the same way in all PHP versions that support the incrementation operator. :)

Order of evaluation isn't a precedence issue. It has nothing to do with operators. The problem also happens with function calls.
By the way, $a++ returns the old value of $a. In your example, $a++ evaluates to 1, not 2.
In the following example, PHP does not define which subexpression is evaluated first: $a or $a++.
$a = 1;
f($a, $a++); //either f(1,1) or f(2,1)
Precedence is about where you put in parentheses. Order of evaluation can't be changed by parentheses. To fix order of evaluation problems, you need to break the code up into multiple lines.
$a = 1;
$a0 = $a;
$a1 = $a++;
f($a0, $a1); //only f(1,1)
Order of evaluation only matters when your subexpressions can have side-effects on each other: the value of one subexpression can change if another subexpression is evaluated first.

Related

php same result adding different number of vars

Can anybody explain why these 2 produce the same result?
$a = 1;
$c = $a + $a++;
var_dump($c);//int(3)
and
$a = 1;
$c = $a + $a + $a++;
var_dump($c);//int(3)
Tested in PHP 7.1. Reviewed Opcode dumps for both cases but still cant get the point. If we add more $a vars to the expression, it produces the expected result.
From PHP: Operator Precedence:
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.
Example #2 Undefined order of evaluation
$a = 1;
echo $a + $a++; // may print either 2 or 3
$i = 1;
$array[$i] = $i++; // may set either index 1 or 2
So in your first example, PHP is obviously returning 1 for $a++ then incrementing it to 2 and then adding the new $a, which is 2.
In your second example, PHP is returning 1 for $a then adding $a then adding $a and then incrementing it to 2.
As can be seen here: https://3v4l.org/kvrTr:
PHP 5.1.0 - 7.1.0
int(3)
int(3)
PHP 4.3.0 - 5.0.5
int(2)
int(3)

String concatenation while incrementing

This is my code:
$a = 5;
$b = &$a;
echo ++$a.$b++;
Shouldn't it print 66?
Why does it print 76?
Alright. This is actually pretty straight forward behavior, and it has to do with how references work in PHP. It is not a bug, but unexpected behavior.
PHP internally uses copy-on-write. Which means that the internal variables are copied when you write to them (so $a = $b; doesn't copy memory until you actually change one of them). With references, it never actually copies. That's important for later.
Let's look at those opcodes:
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > ASSIGN !0, 5
3 1 ASSIGN_REF !1, !0
4 2 PRE_INC $2 !0
3 POST_INC ~3 !1
4 CONCAT ~4 $2, ~3
5 ECHO ~4
6 > RETURN 1
The first two should be pretty easy to understand.
ASSIGN - Basically, we're assinging the value of 5 into the compiled variable named !0.
ASSIGN_REF - We're creating a reference from !0 to !1 (the direction doesn't matter)
So far, that's straight forward. Now comes the interesting bit:
PRE_INC - This is the opcode that actually increments the variable. Of note is that it returns its result into a temporary variable named $2.
So let's look at the source code behind PRE_INC when called with a variable:
static int ZEND_FASTCALL ZEND_PRE_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zend_free_op free_op1;
zval **var_ptr;
SAVE_OPLINE();
var_ptr = _get_zval_ptr_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);
if (IS_VAR == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
}
if (IS_VAR == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(&EG(uninitialized_zval));
AI_SET_PTR(&EX_T(opline->result.var), &EG(uninitialized_zval));
}
if (free_op1.var) {zval_ptr_dtor(&free_op1.var);};
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
&& Z_OBJ_HANDLER_PP(var_ptr, get)
&& Z_OBJ_HANDLER_PP(var_ptr, set)) {
/* proxy object */
zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
Z_ADDREF_P(val);
fast_increment_function(val);
Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
zval_ptr_dtor(&val);
} else {
fast_increment_function(*var_ptr);
}
if (RETURN_VALUE_USED(opline)) {
PZVAL_LOCK(*var_ptr);
AI_SET_PTR(&EX_T(opline->result.var), *var_ptr);
}
if (free_op1.var) {zval_ptr_dtor(&free_op1.var);};
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}
Now I don't expect you to understand what that's doing right away (this is deep engine voodoo), but let's walk through it.
The first two if statements check to see if the variable is "safe" to increment (the first checks to see if it's an overloaded object, the second checks to see if the variable is the special error variable $php_error).
Next is the really interesting bit for us. Since we're modifying the value, it needs to preform copy-on-write. So it calls:
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
Now, remember, we already set the variable to be a reference above. So the variable is not separated... Which means everything we do to it here will happen to $b as well...
Next, the variable is incremented (fast_increment_function()).
Finally, it sets the result as itself. This is copy-on-write again. It's not returning the value of the operation, but the actual variable. So what PRE_INC returns is still a reference to $a and $b.
POST_INC - This behaves similarly to PRE_INC, except for one VERY important fact.
Let's check out the source code again:
static int ZEND_FASTCALL ZEND_POST_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
retval = &EX_T(opline->result.var).tmp_var;
ZVAL_COPY_VALUE(retval, *var_ptr);
zendi_zval_copy_ctor(*retval);
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
fast_increment_function(*var_ptr);
}
This time I cut away all of the non-interesting stuff. So let's look at what it's doing.
First, it gets the return temporary variable (~3 in our code above).
Then it copies the value from its argument (!1 or $b) into the result (and hence the reference is broken).
Then it increments the argument.
Now remember, the argument !1 is the variable $b, which has a reference to !0 ($a) and $2, which if you remember was the result from PRE_INC.
So there you have it. It returns 76 because the reference is maintained in PRE_INC's result.
We can prove this by forcing a copy, by assigning the pre-inc to a temporary variable first (through normal assignment, which will break the reference):
$a = 5;
$b = &$a;
$c = ++$a;
$d = $b++;
echo $c.$d;
Which works as you expected. Proof
And we can reproduce the other behavior (your bug) by introducing a function to maintain the reference:
function &pre_inc(&$a) {
return ++$a;
}
$a = 5;
$b = &$a;
$c = &pre_inc($a);
$d = $b++;
echo $c.$d;
Which works as you're seeing it (76): Proof
Note: the only reason for the separate function here is that PHP's parser doesn't like $c = &++$a;. So we need to add a level of indirection through the function call to do it...
The reason I don't consider this a bug is that it's how references are supposed to work. Pre-incrementing a referenced variable will return that variable. Even a non-referenced variable should return that variable. It may not be what you expect here, but it works quite well in almost every other case...
The Underlying Point
If you're using references, you're doing it wrong about 99% of the time. So don't use references unless you absolutely need them. PHP is a lot smarter than you may think at memory optimizations. And your use of references really hinders how it can work. So while you think you may be writing smart code, you're really going to be writing less efficient and less friendly code the vast majority of the time...
And if you want to know more about References and how variables work in PHP, checkout One Of My YouTube Videos on the subject...
I think the full concatenate line is first executed and than send with the echo function.
By example
$a = 5;
$b = &$a;
echo ++$a.$b++;
// output 76
$a = 5;
$b = &$a;
echo ++$a;
echo $b++;
// output 66
EDIT: Also very important, $b is equal to 7, but echoed before adding:
$a = 5;
$b = &$a;
echo ++$a.$b++; //76
echo $b;
// output 767
EDIT: adding Corbin example: https://eval.in/34067
There's obviously a bug in PHP. If you execute this code:
<?php
{
$a = 5;
echo ++$a.$a++;
}
echo "\n";
{
$a = 5;
$b = &$a;
echo ++$a.$b++;
}
echo "\n";
{
$a = 5;
echo ++$a.$a++;
}
You get:
66 76 76
Which means the same block (1st and 3rd one are identical) of code doesn't always return the same result. Apparently the reference and the increment are putting PHP in a bogus state.
https://eval.in/34023

Confusion over the value of a variable after running a PHP program

I'm studying for my finals and I came across this question:
consider this following PHP code, write the output after executing it
<?php
$a=3;
$b=$a++;
IF($a>$b)
{
echo "a>$b";
}
else if ($a == $b)
{
echo "a=$b";
}
else
{
echo "a < $b";
}
?>
When I output it in my text editor I get a < 3, but I don't understand why though?
I thought a is assigned to 3 and also b is assigned to a++ 3 and 3==3 so should a==3 be printed out?
No, you are using post-increment operator on $a. So, $b will be assigned a value of 3, and later, when the statement is executed, $a will increment itself by one, and become 4. So, you'll now be comparing $a as 4 and $b as 3.
Hence you get the result a > 3
The $a++ incrementation happens after the expression gets evaluated, while ++$a would happen before.
So in your case, $b was first set to 3, and then $a was increased.
$a++ tells the variable $a explicitely to increase, no matter if you assigning to another variable or not!
This gives the possibility to do things like if ($a++ > 10) { // ... in loops.
For your case you would have to take $b = $a + 1;
<?php
$a=3;
$b=$a++;
// $b = 3 and $a = 4 now
IF($a>$b)
{
echo "a>$b";
}
else if ($a == $b)
{
echo "a=$b";
}
else
{
echo "a < $b";
}
?>
I tested your code and I get:
a>3
which makes sense
$a is 3 but is increased to 4 when you do $a++
$b is just $a before the ++ action so it stays 3
Think of $a++ as $a = $a + 1 then it makes sense

Operator precedence issue in Perl and PHP

PHP:
$a = 2;
$b = 3;
if($b=1 && $a=5)
{
$a++;
$b++;
}
echo $a.'-'.$b;
$a = 2;
$b = 3;
if($a=5 and $b=1)
{
$a++;
$b++;
}
echo $a.'-'.$b;
Output 6-16-2.I don't understand the 1 here.
Perl :
$a = 2;
$b = 3;
if($b=1 && $a=5)
{
$a++;
$b++;
}
print $a.'-'.$b;
$a = 2;
$b = 3;
if($a=5 and $b=1)
{
$a++;
$b++;
}
print $a.'-'.$b;
Output 6-66-2, I don't understand the second 6 here.
Anyone knows the reason?
Actually I know && has higher precedence than and,but I still has the doubt when knowing this before hand.
UPDATE
Now I understand the PHP one,what about the Perl one?
Regarding Perl:
Unlike PHP (but like Python, JavaScript, etc.) the boolean operators don't return a boolean value but the value that made the expression true (or the last value) determines the final result of the expression† (source).
$b=1 && $a=5
is evaluated as
$b = (1 && $a=5) // same as in PHP
which is the same as $b = (1 && 5) (assignment "returns" the assigned value) and assigns 5 to $b.
The bottom line is: The operator precedence is the same in Perl and PHP (at least in this case), but they differ in what value is returned by the boolean operators.
FWIW, PHP's operator precedence can be found here.
What's more interesting (at least this was new to me) is that PHP does not perform type conversion for the increment/decrement operators.
So if $b is true, then $b++ leaves the value as true, while e.g. $b += 1 assigns 2 to $b.
†: What I mean with this is that it returns the first (leftmost) value which
evaluates to false in case of &&
evaluates to true in case of ||
or the last value of the expression.
First example
$a = 2;
$b = 3;
if($b=1 && $a=5) // means $b = (1 && $a=5)
{
var_dump($b); //bool(true) because of &&
$a++;
$b++; //bool(true)++ ==true, ok
}
echo $a.'-'.$b;
hope you will not use those codes in production)
I'm noob in perl but i can suggest a&&b returns a or b (last of them if all of them converted to bool), not boolean, then $b = (1 && $a=5) returns $b=5 (is 5)
here's the issue: 1 && 5 returns 5 in perl. you get the result you expect if you code the conditional as if(($b=1) && ($a=5))
For Perl, fig. 2: and has a very low priority in perl, it's not a synonym of &&'s. Therefore the sample is executed as (($a = 5) and ($b = 1)) which sets $a and $b to 5 and 1 respectively and returns a value of the last argument (i.e. 1).
After ++'s you get 6-2.
refer to http://sillythingsthatmatter.in/PHP/operators.php for good examples

If $a = 5, $b = 'a', what is the value of $$b?

Explain this interview question to me:
Q: If the variable $a is equal to 5 and variable $b is equal to character a, what’s the value of $$b?
A: 5, it’s a reference to existing variable.
That's a variable variable. PHP will look up the variable with the name stored in the string $b. So if $b == 'a' then $$b == $a.
It's a lot like pointers in C, except they use variable name strings instead of memory addresses to point to each other. And you can dereference as many times as you want:
$a = 5;
foreach (range('b', 'z') as $L) {
$$L = chr(ord($L) - 1);
}
echo $$$$$$$$$$$$$$$$$$$$$$$$$$z;
Output:
5
-95 is the answer as if u will echo $b u will get output as
"a"
and if u echo $a u will get out but as "5"
hence in this sense when u $(echo $b) which same as $(a) hence u will get it as "5-100" which is "-95"
$$b - 100
= $a - 100 // substituting $b=a
= 5 - 100
= -95
I don't know if the '?' is erroneous in the statement '$$b? - 100' but I don't think that will compile.
However:
$a = 5
$b = 'a';
$c = $$b - 100;
$c will equal -95, because $$b is a variable variable reference and given that $a = 5 it resolves to $a (5) - 100, or -95.
the answer is -95
$a - 100
The following is a good reference on PHP variables
http://php.net/manual/en/language.variables.variable.php

Categories