深入解析php中的foreach問題(2)_PHP教程

      編輯Tag賺U幣
      教程Tag:暫無Tag,歡迎添加,賺取U幣!

      推薦:PHP做好防盜鏈的設置方法
      盜鏈 是指服務提供商自己不提供服務的內容,通過技術手段繞過其它有利益的最終用戶界面(如廣告),直接在自己的 網站上向最終用戶提供其它服務提供商的服務內容,騙取最終用戶的瀏覽和點擊率。受益者不提供資源或提供很少的資源,而真正的服務提供商卻得不到任何的收 益


      這里主要將2個重要的指針存入了zend_execute_data->Ts中:
      •EX_T(opline->result.u.var).var ---- 指向array的指針
      •EX_T(opline->result.u.var).fe.fe_pos ---- 指向array內部元素的指針
      FE_RESET指令執行完畢之后,內存中實際情況如下:

      03.png

      接下來我們繼續查看FE_FETCH,它對應的執行函數為ZEND_FE_FETCH_SPEC_VAR_HANDLER:

      復制代碼 代碼如下:
      static int ZEND_FASTCALL ZEND_FE_FETCH_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
      {
      zend_op *opline = EX(opline);

      // 注意指針是從EX_T(opline->op1.u.var).var.ptr獲取的
      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);

      // 特別注意:
      // FE_RESET指令中將數組內部元素的指針保存在EX_T(opline->op1.u.var).fe.fe_pos
      // 此處獲取該指針
      zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos);

      // 獲取元素的值
      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);
      }

      // 數組內部指針移動到下一個元素
      zend_hash_move_forward(fe_ht);

      // 移動之后的指針保存到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:
      ……
      }

      ……
      }


      根據FE_FETCH的實現,我們大致上明白了foreach($arr as $k => $v)所做的事情。它會根據zend_execute_data->Ts的指針去獲取數組元素,在獲取成功之后,將該指針移動到下一個位置再重新保存。

      04.png

      簡單來說,由于第一遍循環中FE_FETCH中已經將數組的內部指針移動到了第二個元素,所以在foreach內部調用key($arr)和current($arr)時,實際上獲取的便是1和'b'。
      那為何會輸出3遍1=>b呢?
      我們繼續看第9行和第13行的SEND_REF指令,它表示將$arr參數壓棧。緊接著一般會使用DO_FCALL指令去調用key和current函數。PHP并非被編譯成本地機器碼,因此php采用這樣的opcode指令去模擬實際CPU和內存的工作方式。
      查閱PHP源碼中的SEND_REF:

      復制代碼 代碼如下:
      static int ZEND_FASTCALL ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
      {
      ……
      // 從CV中獲取$arr指針的指針
      varptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);
      ……

      // 變量分離,此處重新copy了一份array專門用于key函數
      SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
      varptr = *varptr_ptr;
      Z_ADDREF_P(varptr);

      // 壓棧
      zend_vm_stack_push(varptr TSRMLS_CC);
      ZEND_VM_NEXT_OPCODE();
      }


      上述代碼中的SEPARATE_ZVAL_TO_MAKE_IS_REF是一個宏:

      復制代碼 代碼如下:
      #define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) \
      if (!PZVAL_IS_REF(*ppzv)) { \
      SEPARATE_ZVAL(ppzv); \
      Z_SET_ISREF_PP((ppzv)); \
      }


      SEPARATE_ZVAL_TO_MAKE_IS_REF的主要作用為,如果變量不是一個引用,則在內存中copy出一份新的。本例中它將array('a','b','c')復制了一份。因此變量分離之后的內存為:05.png
      注意,變量分離完成之后,CV數組中的指針指向了新copy出來的數據,而通過zend_execute_data->Ts中的指針則依然可以獲取舊的數據。
      接下來的循環就不一一贅述了,結合上圖來說:
      •foreach結構使用的是下方藍色的array,會依次遍歷a,b,c
      •key、current使用的是上方黃色的array,它的內部指針永遠指向b
      至此我們明白了為何key和current一直返回array的第二個元素,由于沒有外部代碼作用于copy出來的array,它的內部指針便永遠不會移動。
      問題3:

      復制代碼 代碼如下:
      $arr = array('a','b','c');
      foreach($arr as $k => &$v) {
      echo key($arr), '=>', current($arr);
      }// 打印 1=>b 2=>c =>

      分享:如何使用PHP實現javascript的escape和unescape函數
      前端開發工程師都知道javascript有編碼函數escape()和對應的解碼函數unescape(),而php中只有個urlencode和 urldecode,這個編碼和解碼函數對encodeURI和encodeURIComponent有效,但是對escape的是無效的。 javascript中的escape()函數和unescape()函數用戶字符串編碼

      來源:模板無憂//所屬分類:PHP教程/更新時間:2013-07-02
      相關PHP教程