Foreach in php

Source: Internet
Author: User

Preface:

The foreach structure is introduced in php4, which is a simple way to traverse arrays. Compared with the traditional for loop, foreach can more easily obtain key-value pairs. Before php5, foreach can only be used as an array; after php5, foreach can also be used to traverse objects (see traversing objects ). This article only discusses how to traverse arrays.

Although foreach is simple, it may lead to some unexpected behaviors, especially when the Code involves references.

The following lists several cases to help us further understand the nature of foreach.

Question 1:
$arr = array(,,($arr  $k => &= $v * ($arr  $k =>, , 

First, start from a simple one. If we try to run the above Code, we will find that the final output is0 => 2 1 => 4 2 => 4.

Why not 0 => 2 1 => 4 2 => 6?

In fact, we can think that (=> implies the following operation, respectively assigning the current 'key' and current 'value' of the array to the variables $ k and $ v. The specific expansion is shown in the following figure:

(   =>  =     =    

Based on the above theory, we will analyze the first foreach again:

Because $ v is a reference, $ v = & $ arr [0], $ v = $ v * 2 is equivalent to $ arr [0] * 2, therefore, $ arr is changed to 2, 2, and 3.

2nd cycles, $ v = & $ arr [1], $ arr becomes 2, 4, 3

3rd cycles, $ v = & $ arr [2], $ arr is changed to 2, 4, and 6

Then the Code enters the second foreach:

1st cycles, the implicit operation $ v = $ arr [0] is triggered, because $ v is still referenced by $ arr [2, it is equivalent to $ arr [2] = $ arr [0], and $ arr is changed to 2, 4, and 2.

2nd cycles, $ v = $ arr [1], that is, $ arr [2] = $ arr [1], $ arr is changed to 2, 4

3rd cycles, $ v = $ arr [2], that is, $ arr [2] = $ arr [2], $ arr is changed to 2, 4

OK. The analysis is complete.

How can this problem be solved? There is a reminder in the php manual:

Warning: $ value reference of the last element of the array is retained after the foreach loop. We recommend that you use unset () to destroy it.
 = (1,2,3(   => & =  * 2((   =>  "", " => ", ""

From this issue, we can see that references are likely to be accompanied by side effects. If you do not want to cause changes to the array content due to unintentional modifications, it is best to unset these references in time.

Question 2:
= ('A', 'B', 'C' (=> (), "=> ",()
// Print 1 => B 1 => B 1 => B

This problem is even more strange. According to the manual, the key and current are respectively the key values of the current element in the array.

So why is the key ($ arr) always 1 and the current ($ arr) always B?

Use vld to view the compiled opcode:

We can see from the ASSIGN command in line 1 that assigns array ('A', 'B', 'C') to $ arr.

Since $ arr is CV and array ('A', 'B', 'C') is TMP, the ASSIGN Command finds the actually executed function as ZEND_ASSIGN_SPEC_CV_TMP_HANDLER. Note that CV is a variable cache added after PHP5.1. It uses an array to save zval **, when the cached variable is used again, you do not need to search for the active symbol table, but directly retrieve it from the CV array. Because the array access speed is much faster than that of the hash table, the efficiency can be improved.

 *opline =*value = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &    zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline-> (IS_CV == IS_VAR && !         value = zend_assign_to_variable(variable_ptr_ptr, value,  (!RETURN_VALUE_UNUSED(&opline->->result.u.).

After the ASSIGN command is complete, the zval ** pointer is added to the CV array, and the Pointer Points to the actual array, which indicates that $ arr has been cached by the CV.

Next, execute the loop operation of the array. Let's look at the FE_RESET command. Its execution function is ZEND_FE_RESET_SPEC_CV_HANDLER:

Array_ptr = _ get_zval_ptr_cv (& opline-> the array pointer is saved to zend_execute_data-> Ts (Ts is used to store temp_variable during code execution) AI_SET_PTR (EX_T (opline-> result. u .). (fe_ht = HASH_OF (array_ptr ))! = Zend_hash_has_more_elements (fe_ht )! = Zend_hash_get_pointer (fe_ht, & EX_T (opline-> result. u.

Here we mainly store two important pointers:

  • EX_T (opline-> result. u. var). var ---- pointer to array
  • EX_T (opline-> result. u. var). fe. fe_pos ---- pointer to the element inside the array

After the FE_RESET command is executed, the actual memory is as follows:

Next, let's continue to check FE_FETCH. Its corresponding execution function is ZEND_FE_FETCH_SPEC_VAR_HANDLER:

 *opline =    zval *array = EX_T(opline->op1.u.). (zend_iterator_unwrap(array, &=            zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.u.             (zend_hash_get_current_data(fe_ht, ( **) &value)==->opcodes+opline->= zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key,             zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.u.

According to the implementation of FE_FETCH, we generally understand what foreach ($ arr as $ k => $ v) does. It obtains the array elements based on the pointer. After obtaining the elements, it moves the pointer to the next position and saves them again.

To put it simply, because the internal pointer of the array has been moved to the second element in FE_FETCH In the first loop, when the foreach internally calls key ($ arr) and current ($ arr, actually, we get 1 and 'B '.

Why does it output 3 times 1 => B?

Let's continue to look at the SEND_REF commands for lines 9th and 13th, which means to push the $ arr parameter to the stack. Then, the DO_FCALL command is generally used to call the key and current functions. PHP is not compiled with the local machine code. Therefore, php uses the opcode command to simulate the actual CPU and memory usage.

Check SEND_REF in the PHP source code:

     varptr_ptr = _get_zval_ptr_ptr_cv(&opline->= *

In the above Code

 SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv)    \     (!PZVAL_IS_REF(*

If the variable is not a reference, copy a new one in the memory. In this example, it copies array ('A', 'B', 'C. Therefore, the memory after variable separation is:

Note: After the variable separation is complete, the pointer in the CV array points to the new copied data, and the pointer in the array can still obtain the old data.

The following loop will not be described in detail. In combination:

  • The foreach structure uses the blue array below, which traverses a, B, and c in sequence.
  • Key and current use the yellow array above, and its internal pointer always points to B

Now we understand why key and current always return the second element of array. Since no external code acts on the copied array, its internal pointer will never move.

Question 3:
 = ('a','b','c'(   => & (), '=>', ()
 

There is only one difference between this question and Question 2: foreach in this question uses references. View this question with VLD and find that it is the same as the opcode compiled by Issue 2 code. Therefore, we use the Issue 2 tracking method to gradually view the implementation of the opcode.

First, foreach will call FE_RESET:

  (opline->extended_value &        array_ptr_ptr = _get_zval_ptr_ptr_cv(&opline-> (array_ptr_ptr == NULL || array_ptr_ptr == &  (Z_TYPE_PP(array_ptr_ptr) ==             (Z_TYPE_PP(array_ptr_ptr) == (opline->extended_value &= *

In question 2, we have analyzed some FE_RESET implementations. Note that in this example, the value obtained by foreach is referenced. Therefore, during execution, FE_RESET will enter another branch that is different from the previous one.

Finally, FE_RESET sets the is_ref of the array to true. At this time, there is only one array of data in the memory.

Next, we will analyze SEND_REF:

     varptr_ptr = _get_zval_ptr_ptr_cv(&opline->= *

Macro SEPARATE_ZVAL_TO_MAKE_IS_REF only separates variables where is_ref = false. Since is_ref = true has been set for array, it will not be copied. In other words, the memory still has only one array data.

Explains why the first two cycles output 1 => B 2 => C. In the first loop FE_FETCH, move the Pointer Forward.

ZEND_API  zend_hash_move_forward_ex(HashTable *ht, HashPosition **current = pos ? pos : &ht-> (**current = (*current)->        

Since the internal pointer has pointed to the last element of the array, moving forward will point to NULL. After pointing the internal pointer to NULL, we call key and current for the array, respectively, return NULL and false, indicating that the call fails. At this time, the echo does not contain any characters.

Question 4:
 = (1, 2, 3 = (  => & *= 2(, ); 

This question has little to do with foreach, but since foreach is involved, let's discuss it together :)

The code first creates an array $ arr, and then assigns the array to $ tmp. In the following foreach loop, modifying $ v will apply to the array $ tmp, but it does not apply to $ arr.

Why?

This is because in php, the value assignment operation copies the value of a variable to another variable, so modifying one does not affect the other.

Topic: This is not applicable to the object type. From PHP5, objects are always assigned values by reference by default. For example:

  = 1 =  = ->foo=100 ->foo; 

Return to the Code in the question. Now we can confirm that $ tmp = $ arr is actually a value copy. The entire $ arr array will be copied to $ tmp. Theoretically, after the value assignment statement is executed, there will be two identical arrays in the memory.

Maybe some colleagues may wonder if the array is large, isn't this kind of operation very slow?

Fortunately, php has a smarter solution. In fact, after $ tmp = $ arr is executed, there is only one array in the memory. View the zend_assign_to_variable implementation in the php source code (from php5.3.26 ):

Inline zval * values (zval ** variable_ptr_ptr, zval * value, * variable_ptr = * (Z_TYPE_P (variable_ptr) = IS_OBJECT & struct (variable_ptr, (Z_DELREF_P (variable_ptr) = *(! (PZVAL_IS_REF (value) & Z_REFCOUNT_P (value)> * variable_ptr_ptr = * variable_ptr = * // value indicates the pointer to the actual array data in $ arr, variable_ptr_ptr is the pointer to the Data Pointer in $ tmp * variable_ptr_ptr = *

It can be seen that $ tmp = $ arr is essentially copying the array pointer, and then automatically adding the array. 1. Use the chart to display the memory at this time, there is still only one array:

Since there is only one array, why does $ arr remain unchanged when $ tmp is modified in the foreach loop?

Continue to check the ZEND_FE_RESET_SPEC_CV_HANDLER function in the PHP source code. This is an opcode handler whose OPCODE is FE_RESET. Before foreach starts, this function points the internal pointer of the array to its first element.

* Opline = * array_ptr, ***** iter = * ce = 0 (opline-> extended_value & =_ get_zval_ptr_ptr_cv (& opline-> op1, EX (Ts ), (array_ptr_ptr = | array_ptr_ptr = & (Z_TYPE_PP (array_ptr_ptr) = (Z_TYPE_PP (array_ptr_ptr) ==/// it will copy an array again. // truly detach $ tmp and $ arr and convert it into two arrays in the memory (opline-> extended_value & = *

From the code, we can see that the true execution of variable separation is not when the value assignment statement is executed, but when the variable is used, this is also the implementation of the Copy On Write mechanism in PHP.

After FE_RESET, the memory changes as follows:

Explains why foreach does not affect the original $ arr. For changes in ref_count and is_ref, you can read the specific implementation of handler and ZEND_SWITCH_FREE_SPEC_VAR_HANDLER (both in php-src/zend/zend_vm_execute.h) in detail :)

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.