In-depth parsing of the foreach problem in PHP _php tutorial

Source: Internet
Author: User
Tags php source code
Objective:
The foreach structure is introduced in PhP4, which is an easy way to iterate through an array. foreach makes it easier to get key-value pairs than a traditional for-loop. Before PhP5, foreach can only be used in arrays, and after php5, it is possible to iterate through the object using foreach (see: Traversing an object). This article only discusses the case of traversing an array.

While foreach is simple, it may cause some unexpected behavior, especially if the code involves references.
Here are a few examples to help us understand the nature of foreach further.
Question 1:
Copy CodeThe code is as follows:
$arr = Array (+/-);
foreach ($arr as $k = & $v) {
$v = $v * 2;
}
Now $arr is Array (2, 4, 6)
foreach ($arr as $k = = $v) {
echo "$k", "= =", "$v";
}

Starting with the simple beginning, if we try to run the above code, we will find that the final output is 0=>2 1=>4 2=>4.
Why not 0=>2 1=>4 2=>6?
In fact, we can assume that the foreach ($arr as $k + $v) structure implies the following actions, assigning the current ' key ' and the current ' value ' of the array to the variables $k and $v, respectively. The specific unfolding shape is as follows:
Copy CodeThe code is as follows:
foreach ($arr as $k = = $v) {
2 assignment actions are implied before user code execution
$v = Currentval ();
$k = Currentkey ();
Continue running user code
......
}

Based on the above theory, we now re-analyze the first foreach:
The 1th cycle, since $v is a reference, so $v = & $arr [0], $v = $v * * is equivalent to $arr[0]*2, so $arr becomes 2,2,3
2nd cycle, $v = & $arr [1], $arr becomes 2,4,3
3rd cycle, $v = & $arr [2], $arr becomes 2,4,6
The code then enters the second foreach:
1th cycle, the implied operation $v= $arr [0] is triggered, because at this time $v is still a reference to $arr[2], which is equivalent to $arr[2]= $arr [0], $arr into 2,4,2
2nd cycle, $v = $arr [1], i.e. $arr[2]= $arr [1], $arr becomes 2,4,4
3rd cycle, $v = $arr [2], i.e. $arr[2]= $arr [2], $arr becomes 2,4,4
OK, the analysis is complete.
How to solve a similar problem? The PHP manual has a reminder:
Warning: The $value reference of the last element of the array remains after the Foreach loop. It is recommended to use unset () to destroy it.
Copy CodeThe code is as follows:
$arr = Array (+/-);
foreach ($arr as $k = & $v) {
$v = $v * 2;
}
Unset ($v);
foreach ($arr as $k = = $v) {
echo "$k", "= =", "$v";
}
Output 0=>2 1=>4 2=>6

As we can see from this question, references are likely to be accompanied by side effects. If you do not want the unconscious modifications to cause the array contents to change, it is best to unset these references in time.
Question 2:
Copy CodeThe code is as follows:
$arr = Array (' A ', ' B ', ' C ');
foreach ($arr as $k = = $v) {
echo Key ($arr), "= =", current ($arr);
}
Print 1=>b 1=>b 1=>b

The problem is even more bizarre. As the manual says, key and current are the key values of the elements in the array.
So why is key ($arr) always 1,current ($arr) always B?
First use VLD to view the compiled opcode:

We look at the Assign command on line 3rd, which represents assigning an array (' A ', ' B ', ' C ') to $arr.
Since $arr is TMP for Cv,array (' A ', ' B ', ' C '), the ASSIGN instruction finds the actual executed function as Zend_assign_spec_cv_tmp_handler. It should be noted here that the CV is a variable cache that is added after PHP5.1, it is used as an array to save the zval**, the cache is used again without the need to find the active symbol table, but directly to the CV array to get, because the array access speed far beyond the hash table , thus improving efficiency.
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_assign_spec_cv_tmp_handler (Zend_opcode_handler_args)
{
Zend_op *opline = EX (opline);
Zend_free_op Free_op2;
Zval *value = _get_zval_ptr_tmp (&opline->op2, EX (Ts), &free_op2 tsrmls_cc);

Create a $arr** pointer in a CV array
Zval **variable_ptr_ptr = _GET_ZVAL_PTR_PTR_CV (&opline->op1, EX (Ts), Bp_var_w tsrmls_cc);
if (IS_CV = = Is_var &&!variable_ptr_ptr) {
......
}
else {
Assigning an array to $arr
Value = zend_assign_to_variable (variable_ptr_ptr, value, 1 tsrmls_cc);
if (! Return_value_unused (&opline->result)) {
Ai_set_ptr (ex_t (Opline->result.u.var). Var, value);
Pzval_lock (value);
}
}
Zend_vm_next_opcode ();
}

After the assign instruction is complete, the zval** pointer is added to the CV array, and the pointer points to the actual array, which means that $arr has been cached by the CV.

Next we perform the loop operation of the array, let's take a look at the fe_reset directive, which corresponds to the execution function Zend_fe_reset_spec_cv_handler:
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_fe_reset_spec_cv_handler (Zend_opcode_handler_args)
{
......
if (...) {
......
} else {
Get a pointer to an array from the CV arrays
Array_ptr = _GET_ZVAL_PTR_CV (&opline->op1, EX (Ts), Bp_var_r tsrmls_cc);
......
}
......
Saves a pointer to an array to Zend_execute_data->ts (TS is used to hold the temp_variable of the Code execution period)
Ai_set_ptr (ex_t (Opline->result.u.var). Var, array_ptr);
Pzval_lock (ARRAY_PTR);
if (ITER) {
......
} else if ((Fe_ht = hash_of (array_ptr)) = NULL) {
Reset array Internal pointers
Zend_hash_internal_pointer_reset (FE_HT);
if (CE) {
......
}
Is_empty = zend_hash_has_more_elements (fe_ht)! = SUCCESS;

Set ex_t (Opline->result.u.var). Fe.fe_pos for holding array internal pointers
Zend_hash_get_pointer (Fe_ht, &ex_t (Opline->result.u.var). Fe.fe_pos);
} else {
......
}
......
}

Here the main 2 important pointers are deposited into the zend_execute_data->ts:
ex_t (Opline->result.u.var). var----pointer to array
ex_t (opline->result.u.var). Fe.fe_pos----Pointer to an array inner element
After the fe_reset instruction is executed, the actual memory is as follows:



Next we continue to look at Fe_fetch, which corresponds to the execution function Zend_fe_fetch_spec_var_handler:
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_fe_fetch_spec_var_handler (Zend_opcode_handler_args)
{
Zend_op *opline = EX (opline);

Note that the pointer is from ex_t (Opline->op1.u.var). Var.ptr Gets the
Zval *array = ex_t (Opline->op1.u.var). Var.ptr;
......

Switch (zend_iterator_unwrap (array, &iter tsrmls_cc)) {
Default
Case Zend_iter_invalid:
......
Case Zend_iter_plain_object: {
......
}
Case Zend_iter_plain_array:
Fe_ht = hash_of (array);

Special attention:
The Fe_reset directive stores pointers to the inner elements of an array in ex_t (Opline->op1.u.var). Fe.fe_pos
Get the pointer here
Zend_hash_set_pointer (Fe_ht, &ex_t (Opline->op1.u.var). Fe.fe_pos);

Get the value of an element
if (Zend_hash_get_current_data (FE_HT, (void * *) &value) ==failure) {
ZEND_VM_JMP (EX (Op_array)->opcodes+opline->op2.u.opline_num);
}
if (Use_key) {
Key_type = ZEND_HASH_GET_CURRENT_KEY_EX (Fe_ht, &str_key, &str_key_len, &int_key, 1, NULL);
}

The array interior pointer moves to the next element
Zend_hash_move_forward (FE_HT);

The pointer after the move is saved to ex_t (Opline->op1.u.var). Fe.fe_pos
Zend_hash_get_pointer (Fe_ht, &ex_t (Opline->op1.u.var). Fe.fe_pos);
Break
Case Zend_iter_object:
......
}

......
}

Based on the implementation of Fe_fetch, we generally understand the things that foreach ($arr as $k = = $v) do. It takes the array element according to Zend_execute_data->ts's pointer, and after it succeeds, moves the pointer to the next location and then resave.

In a nutshell, since the inner pointer of the array has been moved to the second element in the Fe_fetch in the first cycle, when you call key ($arr) and current ($arr) inside the foreach, you actually get 1 and ' B '.
so why would it output 3 times 1=>b?
We continue to look at lines 9th and 13th of the SEND_REF directive, which indicates that the $arr parameter is stacked. The key and current functions are usually called using the Do_fcall directive. PHP is not compiled with the cost of machine code, so PHP uses such opcode instructions to simulate how the actual CPU and memory work.
Check the PHP source for Send_ref:
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_send_ref_spec_cv_handler (Zend_opcode_handler_args)
{
......
Get a pointer to the $arr pointer from the CV
Varptr_ptr = _GET_ZVAL_PTR_PTR_CV (&opline->op1, EX (Ts), Bp_var_w tsrmls_cc);
......

Variable separation, where a copy of the array is copied specifically for the key function
Separate_zval_to_make_is_ref (VARPTR_PTR);
VarPtr = *varptr_ptr;
z_addref_p (VARPTR);

Press Stack
Zend_vm_stack_push (VarPtr tsrmls_cc);
Zend_vm_next_opcode ();
}

The separate_zval_to_make_is_ref in the preceding code is a macro:
Copy CodeThe code is as follows:
#define SEPARATE_ZVAL_TO_MAKE_IS_REF (PPZV) \
if (! Pzval_is_ref (*ppzv)) {\
Separate_zval (PPZV); \
Z_SET_ISREF_PP ((Ppzv)); \
}

The main function of Separate_zval_to_make_is_ref is to copy a new copy in memory if the variable is not a reference. In this example it copies an array (' A ', ' B ', ' C '). So the memory after the variable is separated is:
Note that after the variable separation is complete, the pointer in the CV array points to the newly copied data, while the old data is still available through the pointer in the Zend_execute_data->ts.
the next cycle is not a repeat, combined:
The foreach structure uses the blue array below, which traverses the a,b,c in turn.
key, current uses the yellow array above, and its internal pointer always points to B
Now that we understand why key and current return the second element of the array, the internal pointer will never move because no external code is acting on the copy-out array.
Question 3:
Copy CodeThe code is as follows:
$arr = Array (' A ', ' B ', ' C ');
foreach ($arr as $k = & $v) {
echo Key ($arr), ' = = ', current ($arr);
}//print 1=>b 2=>c =

This is a little bit different from question 2: The foreach in the topic uses references. Use VLD to see the subject, and found the same as the problem 2 code compiled opcode. So we use the tracking method of problem 2, and look at the corresponding implementation of opcode.
First, foreach calls Fe_reset:
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_fe_reset_spec_cv_handler (Zend_opcode_handler_args)
{
......
if (Opline->extended_value & zend_fe_reset_variable) {
Get the variable from the CV
Array_ptr_ptr = _GET_ZVAL_PTR_PTR_CV (&opline->op1, EX (Ts), Bp_var_r tsrmls_cc);
if (array_ptr_ptr = = NULL | | array_ptr_ptr = = &EG (uninitialized_zval_ptr)) {
......
}
else if (z_type_pp (array_ptr_ptr) = = Is_object) {
......
}
else {
For the case of iterating over an array
if (z_type_pp (array_ptr_ptr) = = Is_array) {
Separate_zval_if_not_ref (ARRAY_PTR_PTR);
if (Opline->extended_value & Zend_fe_fetch_byref) {
Set the zval of the Save array to Is_ref
Z_SET_ISREF_PP (ARRAY_PTR_PTR);
}
}
Array_ptr = *array_ptr_ptr;
z_addref_p (ARRAY_PTR);
}
} else {
......
}
......
}

Part of the implementation of Fe_reset has been analyzed in question 2. It is important to note that in this case, the foreach fetch value takes a reference, so at execution time the Fe_reset enters another branch that is different from the previous question.
Finally, Fe_reset sets the is_ref of the array to true, at which time there is only one copy of the array's data.
Next Analyze Send_ref:
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_send_ref_spec_cv_handler (Zend_opcode_handler_args)
{
......
Get a pointer to the $arr pointer from the CV
Varptr_ptr = _GET_ZVAL_PTR_PTR_CV (&opline->op1, EX (Ts), Bp_var_w tsrmls_cc);
......

Variable separation, since the variable in the CV itself is a reference, this does not copy a new array
Separate_zval_to_make_is_ref (VARPTR_PTR);
VarPtr = *varptr_ptr;
z_addref_p (VARPTR);

Press Stack
Zend_vm_stack_push (VarPtr tsrmls_cc);
Zend_vm_next_opcode ();
}

The macro separate_zval_to_make_is_ref only separates Is_ref=false variables. Since the previous array has been set to is_ref=true, it will not be copied a copy. In other words, there is still only one copy of the array data in memory at this time.

Explains why the first 2 cycles output 1=>b 2=>c. When the 3rd cycle Fe_fetch, the pointer continues to move forward.
Copy CodeThe code is as follows:
Zend_api int zend_hash_move_forward_ex (HashTable *ht, hashposition *pos)
{
Hashposition *current = pos? POS: &ht->pInternalPointer;
Is_consistent (HT);
if (*current) {
*current = (*current)->plistnext;
return SUCCESS;
} else
return FAILURE;
}

Because the internal pointer already points to the last element of the array, moving forward will point to null. After you point the internal pointer to null, we then call the key and current on the array, and return null and false, respectively, to indicate that the call failed with the Echo character.
Question 4:
Copy CodeThe code is as follows:
$arr = Array (1, 2, 3);
$tmp = $arr;
foreach ($tmp as $k = & $v) {
$v *= 2;
}
Var_dump ($arr, $tmp); Print What?

The problem has little to do with foreach, but since it involves foreach, discuss it together:)
The code first creates the array $arr, and then assigns the array to $tmp, and in the next foreach loop, modifying the $v will work on the array $tmp, but it does not work for $arr.
for what?
This is because in PHP, the assignment operation is to copy the value of one variable into another variable, so modifying one does not affect the other.
Off-topic: this does not apply to object types, and from PHP5, objects are always assigned by reference by default, for example:
Copy CodeThe code is as follows:
Class a{
public $foo = 1;
}
$a 1 = $a 2 = new A;
$a 1->foo=100;
Echo $a 2->foo; Output, $a 1 is actually a reference to the same object as $a2

Back to the code in the title, we can now determine that the $tmp= $arr is actually a value copy, and the entire $arr array will be copied to $tmp. Theoretically, after the assignment statement is executed, there will be 2 identical arrays in memory.
Perhaps there is a reunion question, if the array is large, will not this operation is very slow?
Fortunately, PHP has a smarter way of handling it. In fact, when the $tmp= $arr executes, there is still only one array in memory. See the Zend_assign_to_variable implementation in PHP source code (from php5.3.26):
Copy CodeThe code is as follows:
Static inline zval* zend_assign_to_variable (zval **variable_ptr_ptr, zval *value, int is_tmp_var tsrmls_dc)
{
Zval *variable_ptr = *variable_ptr_ptr;
Zval Garbage;
......
The left value is the type of object
if (z_type_p (variable_ptr) = = Is_object && z_obj_handler_p (variable_ptr, set)) {
......
}
Case where the lvalue is a reference
if (Pzval_is_ref (variable_ptr)) {
......
} else {
The case of the left value Refcount__gc=1
if (z_delref_p (variable_ptr) ==0) {
......
} else {
Gc_zval_check_possible_root (*VARIABLE_PTR_PTR);
Non-temp variable
if (!is_tmp_var) {
if (pzval_is_ref (value) && z_refcount_p (value) > 0) {
Alloc_zval (VARIABLE_PTR);
*variable_ptr_ptr = variable_ptr;
*variable_ptr = *value;
Z_set_refcount_p (variable_ptr, 1);
Zval_copy_ctor (VARIABLE_PTR);
} else {
$tmp = $arr will run here,
Value is a pointer to the actual array data in the $arr, Variable_ptr_ptr is a pointer to the data pointer in the $TMP
Just copy the pointer and not actually copy the actual array
*variable_ptr_ptr = value;
Value of REFCOUNT__GC is +1, in this case refcount__gc is 1,z_addref_p after 2
z_addref_p (value);
}
} else {
......
}
}
Z_UNSET_ISREF_PP (VARIABLE_PTR_PTR);
}
return *variable_ptr_ptr;
}

Visible $tmp = The essence of the $arr is to copy the pointer to the array and then automatically add the refcount of the array to 1. Use a graph to express the memory at this time, and still have only one array of arrays:

Since there is only one array, why does $arr not change when $tmp is modified in the Foreach loop?
Continue to see the PHP source in the Zend_fe_reset_spec_cv_handler function, which is a opcode HANDLER, it corresponds to the opcode for fe_reset. The function is responsible for pointing the inner pointer of the array to its first element before the foreach begins.
Copy CodeThe code is as follows:
static int Zend_fastcall Zend_fe_reset_spec_cv_handler (Zend_opcode_handler_args)
{
Zend_op *opline = EX (opline);
Zval *array_ptr, **array_ptr_ptr;
HashTable *fe_ht;
Zend_object_iterator *iter = NULL;
Zend_class_entry *ce = NULL;
Zend_bool is_empty = 0;
To fe_reset a variable
if (Opline->extended_value & zend_fe_reset_variable) {
Array_ptr_ptr = _GET_ZVAL_PTR_PTR_CV (&opline->op1, EX (Ts), Bp_var_r tsrmls_cc);
if (array_ptr_ptr = = NULL | | array_ptr_ptr = = &EG (uninitialized_zval_ptr)) {
......
}
foreach an Object
else if (z_type_pp (array_ptr_ptr) = = Is_object) {
......
}
else {
This regular session goes to this branch
if (z_type_pp (array_ptr_ptr) = = Is_array) {
Note the separate_zval_if_not_ref here
It will re-copy an array out
True separation of $tmp and $arr into 2 arrays in memory
Separate_zval_if_not_ref (ARRAY_PTR_PTR);
if (Opline->extended_value & Zend_fe_fetch_byref) {
Z_SET_ISREF_PP (ARRAY_PTR_PTR);
}
}
Array_ptr = *array_ptr_ptr;
z_addref_p (ARRAY_PTR);
}
} else {
......
}

Reset array Internal pointers
......
}

As can be seen from the code, the actual execution of the variable is not in the execution of the assignment statement, but rather deferred to the use of variables, which is the copy on the write mechanism in PHP implementation.
After Fe_reset, the memory changes as follows:


Explains why foreach does not have an effect on the original $arr. As for the change of Ref_count and Is_ref, interested students can read Zend_fe_reset_spec_cv_handler and zend_switch_free_spec_var_ in detail. Handler specific implementations (all located in php-src/zend/zend_vm_execute.h), this article does not do a detailed analysis:)

http://www.bkjia.com/PHPjc/327985.html www.bkjia.com true http://www.bkjia.com/PHPjc/327985.html techarticle The foreach structure is introduced in PhP4, which is a simple way to iterate through an array. foreach makes it easier to get key-value pairs than a traditional for-loop. Before php5, foreach only ...

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.