$a = 1; $a++; # var_dump($a); $b = 1; $b += 1; # var_dump($b); $c = 1; $c = $c + 1; # var_dump($c);
int(2) int(2) int(2)
$a++
and $a += 1
. But let's look at another example: $a = "foo"; $a++; var_dump($a); $a = "foo"; $a += 1; var_dump($a); $a = "foo"; $a = $a + 1; var_dump($a); string(3) "fop" int(1) int(1)
int(1)
? Where did they come from? From the point of view of a PHP developer, this looks very inconsistent, and it turns out that our three ways of incrementing are unequal. Let's see what happens in the depths of PHP when executing code. compiled vars: !0 = $a, !1 = $b, !2 = $c line #* EIO op fetch ext return operands --------------------------------------------------------------------------- 3 0 E > ASSIGN !0, 1 4 1 POST_INC ~1 !0 2 FREE ~1 5 3 SEND_VAR !0 4 DO_FCALL 1 'var_dump' 7 5 ASSIGN !1, 1 8 6 ASSIGN_ADD 0 !1, 1 9 7 SEND_VAR !1 8 DO_FCALL 1 'var_dump' 11 9 ASSIGN !2, 1 12 10 ADD ~7 !2, 1 11 ASSIGN !2, ~7 13 12 SEND_VAR !2 13 DO_FCALL 1 'var_dump' 14 > RETURN 1
compiled vars: !0 = $a, !1 = $b, !2 = $c line #* EIO op fetch ext return operands --------------------------------------------------------------------------- 4 1 POST_INC ~1 !0 2 FREE ~1 8 6 ASSIGN_ADD 0 !1, 1 12 10 ADD ~7 !2, 1 11 ASSIGN !2, ~7
$a++
turns into two opcodes ( POST_INC FREE
), $a += 1
- into one ( ASSIGN_ADD
) and $a = $a + 1
too, into two. Please note that in all three cases, different opcodes are obtained, which already implies a different execution of PHP.$a++
). This PHP code is converted to the POST_INC
opcode. By the way, PRE_INC
obtained from ++$a
, and you need to know the difference between them. The second opcode, FREE
, clears the result after POST_INC
, because we do not use its return value: POST_INC
changes the current operand in place. In this case, you can ignore this opcode.zend_vm_def.h
, which you can find in the source C code of PHP. This is a large header file filled with macros, so it is not so easy to read, even if you know C. When you call the POST_INC POST_INC
, the contents of line 971 are executed.$a
in the PHP code, which in the byte code turns into !0
) belongs to the type long
. In essence, the system checks whether the variable contains a number. Although PHP is a language with dynamic typing, each variable still belongs to some “type”. Types may change, as we will see later. If our variable belongs to long
, then the C-function fast_increment_function()
is fast_increment_function()
and the return to the next opcode occurs.$a = "foobar"
; $a[2]++
, we get an error.__get
and __set
magic PHP methods. If so, then the correct value is retrieved using __get
, fast_increment_function()
is fast_increment_function()
and the value is saved by calling the __set
method. These methods are called from C, not from PHP.increment_function()
is simply called.fast_increment_function
, and if this is a magic property, then to a call to increment_function()
. Below we talk about the operation of these functions.fast_increment_function()
function belongs to zend-operators , and its task is to accelerate a specific variable as quickly as possible.long
type, then a very fast assembly language is used to increment it. If the value has reached the maximum number of type INT ( LONG_MAX
), then the variable is automatically converted to double ( double
). This is the fastest way to increase the number, since this part of the code is written in assembler. It is believed that the compiler can not optimize the C-code better than the assembler. But the method only works if the variable is of type long
. Otherwise, the function will be redirected to the increment_function()
function. Since incrementing (and decrementing) is most often done in very small inner loops (for example, for
), you need to do this as quickly as possible in order to maintain high PHP performance.fast_increment_function()
is a fast way to increment a number, then increment_function
is a slow ( slow
) way. The process scenario also depends on the type of the variable.long
type, then the number simply increases (and is converted to double
when the maximum value is reached, which can no longer be stored in long
). Most often this will be done with the help of fast_increment_function
, but it may happen that long
will be transferred to this function anyway, so a check is necessary here.double
, then it simply increases.NULL
, then long 1
always returned.string
, then the magic described above is applied.internal
operator, then the add
operator is called to add long 1
. Note that this only works for the internal
classes that manually define these operator functions; you cannot define object operators in the user space PHP code. It implements a single class in the source PHP code - GMP
. So you can make $a = new gmp(1) + new gmp(3); // gmp(4)
$a = new gmp(1) + new gmp(3); // gmp(4)
. Such an opportunity appeared since PHP 5.6, but operator overloading is impossible in PHP directly.$a = false; $a++
$a = false; $a++
not only will not work, but even the error will not return. The variable simply does not change, but remains false
.long (int(123)
). When converting, several tricks are used:0x123
) are supported.0123
and b11
) are not supported.1E5
).double
.135abc
or ab123
) are not supported and are not considered to be numbers.long
or double
, the number simply increases. For example, if we take the string 123 and increment, we get an int(124)
. Note that the variable type changes from string to integer!long
or double
, then the function increment_string()
called.string("1")
. Otherwise, the carry system is used to increment the string.a
to z
, then it is incremented ( a
becomes b
, and so on). If the character is z
, then it changes to a
and is “transferred” one position ahead of the current one.a
becomes b
, ab
becomes ac
(transfer is not needed), az
becomes ba
( z
becomes a
, a
becomes b
, because we carry one character).A
to Z
, as well as to numbers from 0
to 9
. When incrementing 9
turns into 0
and is transferred to the previous position. "z" => "aa" "9" => "00" "Zz" => "AAa" "9z" => "10a"
string("2D9")
will string("2D9")
string("2E0")
( string("2D9"
) is not a number, so the usual string will be incremented). But when string("2E0")
incremented, you already get double(3)
, because 2E0
is a scientific representation 2
and it will be converted to double
, which can then be incremented to 3. So be careful with the increment cycles!Z
? And if so, then with the new increment we will get AA
. In other words, we cannot simply remove characters during decrementing, as we add them when incrementing.$a += 1
). It looks similar to the unary increment operator, but behaves differently in terms of the generated opcodes and actual execution. The expression is fully processed using zend_binary_assign_op_helper , which, after a series of checks, calls add_function with two operands: $a
and our int(1)
value.add_function
method works differently depending on the types of variables. For the most part, it consists of checking the types of operands:long
, then their values ​​simply increase (when overflow is converted to double
).long
and the second is double
, then both are converted to double
and incremented.double
, then they are simply added together.$a = [ 'a', 'b' ] + [ 'c', 'd' ];
. It will turn out [ 'a', 'b']
, as if they combined the second array, but they had the same keys. Note that the union is not on the values, but on the keys.increment_function()
method). You can't do this in PHP yourself, this is only supported by internal classes like GMP.string + long
), then using the zendi_convert_scalar_to_number
method, both of them will be converted to scalars. After the conversion, the add_function
function will be applied add_function
, and this time a match will probably be found for one of the described pairs.is_numeric_string
check if it contains a number. If not, an int(0)
returned.null
or boolean false
, then int(0)
returned.true
, then int(1)
returned.long
(as is the case with internal operators, there may be internal cast functionality, but it is not always implemented and is available only for main classes, and not for PHP- classes in user space).fast_add_function()
function is fast_add_function()
. Like fast_increment_function()
, it directly uses the assembler code to increment numbers if both operands are long
or double
. If this is not the case, then the function is redirected to the add_function()
function used by the assignment expression.$a = $a + 1 $a += 1
work the same way. The only difference is that the addition operator CAN execute quickly if both operands are long
or double
. So if you want to micro-optimize, $a = $a + 1
will work faster than $a += 1
. Not only thanks to fast_add_function()
, but also because we don’t need to process additional bytecode to save the results back to $a
.add_function
converts types to compatible pairs, and increment_function
does not. Now we can explain the results obtained: $a = false; $a++; var_dump($a); // bool(false) $a = false; $a += 1; var_dump($a); // int(1) $a = false; $a = $a + 1; var_dump($a); // int(1)
increment_function
does not convert a boolean value (this is not a number or a string that can be converted to a number), a silent failure occurs and the value is not incremented. Therefore, it remains a bool(false)
. In the case of add_function
, an attempt is made to find a matching pair of boolean
and long
, which does not exist. As a result, both values ​​are converted to long
: bool(false)
becomes int(0)
, and int(1)
remains int(1)
. Now we have a pair of long
& long
, so add_function
simply summarizes them and it turns out int(1)
. (Question: what will the boolean true
+ int(1)
turn into?) $a = "foo"; $a++; var_dump($a); // string("fop") $a = "foo"; $a += 1; var_dump($a); // int(1) $a = "foo"; $a = $a + 1; var_dump($a); // int(1)
long
after checking for the presence of numbers. Since there are none, then the string is converted to int(0)
and int(1)
added to it.Source: https://habr.com/ru/post/305906/
All Articles