深入解析PHP中foreach語句控制數組循環

來源:文萃谷 2.81W

深入解析PHP中foreach語句控制數組循環的用法,是PHP入門學習中的基礎知識,需要的朋友可以參考下。

深入解析PHP中foreach語句控制數組循環

  foreach是PHP中很常用的一個用作數組循環的控制語句

因為它的方便和易用,自然也就在後端隱藏着很複雜的具體實現方式(對用户透明)

今天,我們就來一起分析分析,foreach是如何實現數組(對象)的遍歷的。

我們知道PHP是一個腳本語言,也就是説,用户編寫的PHP代碼最終都是會被PHP解釋器解釋執行,

特別的,對於PHP來説,所有的用户編寫的PHP代碼,都會被翻譯成PHP的虛擬機ZE的虛擬指令(OPCODES)來執行,不論細節的話,就是説,我們所編寫的任何PHP腳本,都會最終被翻譯成一條條的指令,從而根據指令,由相應的C編寫的函數來執行。

那麼foreach會被翻譯成什麼樣子呢?

foreach($arr as $key => $vAL){

echo $key . '=>' . $val . " ";

}

在詞法分析階段,foreach會被識別為一個TOKEN:T_FOREACH,

語法分析階段,會被規則:

unticked_statement: //沒有被綁定ticks的語句

//有省略

| T_FOREACH '(' variable T_AS

{ zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); }

foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }

foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }

| T_FOREACH '(' expr_without_variable T_AS

{ zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); }

variable foreach_optional_arg ')' { zend_check_writable_variable(&$6); zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); }

foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }

//有省略

;

仔細分析這段語法規則,我們可以發現,對於:

foreach($arr as $key => $val){

echo $key . ‘=>' . $val .” ”;

}

會被分析為:

T_FOREACH '(' variable T_AS { zend_do_foreach_begin('foreach', '(', $arr, 'as', 1 TSRMLS_CC); }

foreach_variable foreach_optional_arg(T_DOUBLE_ARROW foreach_variable) ')' { zend_do_foreach_cont('foreach', '(', 'as', $key, $val TSRMLS_CC); }

foreach_satement {zend_do_foreach_end('foreach', 'as');}

然後,讓我們來看看foreach_statement:

它其實就是一個代碼塊,體現了我們的 echo $key . ‘=>' . $val .” ”;

T_ECHO expr;

顯然,實現foreach的核心就是如下3個函數:

zend_do_foreach_begin

zend_do_foreach_cont

zend_do_foreach_end

其中,zend_do_foreach_begin (代碼太長,直接寫偽碼) 主要做了:

1. 記錄當前的opline行數(為以後跳轉而記錄)

2. 對數組進行RESET(講內部指針指向第一個元素)

3. 獲取臨時變量 ($val)

4. 設置獲取變量的OPCODE FE_FETCH,結果存第3步的臨時變量

4. 記錄獲取變量的OPCODES的行數

而對於 zend_do_foreach_cont來説:

1. 根據foreach_variable的來判斷是否引用

2. 根據是否引用來調整zend_do_foreach_begin中生成的FE_FETCH方式

3. 根據zend_do_foreach_begin中記錄的取變量的OPCODES的行數,來初始化循環(主要處理在循環內部的循環:do_begin_loop)

最後zend_do_foreach_end:

1. 根據zend_do_foreach_begin中記錄的行數信息,設置ZEND_JMP OPCODES

2. 根據當前行數,設置循環體下一條opline, 用以跳出循環

3. 結束循環(處理循環內循環:do_end_loop)

4. 清理臨時變量

當然, 在zend_do_foreach_cont 和 zend_do_foreach_end之間 會在語法分析階段被填充foreach_satement的語句代碼。

這樣,就實現了foreach的OPCODES line。

比如對於我們開頭的實例代碼,最終生成的OPCODES是:

filename: /home/huixinchen/

function name: (null)

number of ops: 17

compiled vars: !0 = $arr, !1 = $key, !2 = $val

line # op fetch ext return operands

-------------------------------------------------------------------------------

2 0 SEND_VAL 1

1 SEND_VAL 100

2 DO_FCALL 2 'range'

3 ASSIGN !0, $0

3 4 FE_RESET $2 !0, ->14

5 FE_FETCH $3 $2, ->14

6 ZEND_OP_DATA ~5

7 ASSIGN !2, $3

8 ASSIGN !1, ~5

4 9 CONCAT ~7 !1, '-'

10 CONCAT ~8 ~7, !2

11 CONCAT ~9 ~8, '%0A'

12 ECHO ~9

5 13 JMP ->5

14 SWITCH_FREE $2

7 15 RETURN 1

16* ZEND_HANDLE_EXCEPTION

我們注意到FE_FETCH的op2的操作數是14,也就是JMP後一條opline,也就是説,在獲取完最後一個數組元素以後,FE_FETCH失敗的情況下,會跳到第14行opline,從而實現了循環的結束。

而15行opline的op1的操作數是指向了FE_FETCH,也就是無條件跳轉到第5行opline,從而實現了循環。

附錄:

void zend_do_foreach_begin(znode *foreach_token, znode *open_brackets_token, znode *array, znode *as_token, int variable TSRMLS_DC)

{

zend_op *opline;

zend_bool is_variable;

zend_bool push_container = 0;

zend_op dummy_opline;

if (variable) {

//是否是匿名數組

if (zend_is_function_or_method_call(array)) {

//是否是函數返回值

is_variable = 0;

} else {

is_variable = 1;

}

/* 使用括號記錄FE_RESET的opline行數 */

open_brackets_token->ne_num = get_next_op_number(CG(active_op_array));

zend_do_end_variable_parse(BP_VAR_W, 0 TSRMLS_CC); //獲取數組/對象和zend_do_begin_variable_parse對應

if (CG(active_op_array)->last > 0 &&

CG(active_op_array)->opcodes[CG(active_op_array)->last-1]de == ZEND_FETCH_OBJ_W) {

/* Only lock the container if we are fetching from a real container and not $this */

if (CG(active_op_array)->opcodes[CG(active_op_array)->last-1]_type == IS_VAR) {

CG(active_op_array)->opcodes[CG(active_op_array)->last-1]nded_value |= ZEND_FETCH_ADD_LOCK;

push_container = 1;

}

}

} else {

is_variable = 0;

熱門標籤