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

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

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


      本題與問題2僅有一點區別:本題中的foreach使用了引用。用VLD查看本題,發現與問題2代碼編譯出來的opcode一樣。因此我們采用問題2的跟蹤方法,逐步查看opcode對應的實現。
      首先foreach會調用FE_RESET:

      復制代碼 代碼如下:
      static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
      {
      ……
      if (opline->extended_value & ZEND_FE_RESET_VARIABLE) {
      // 從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 {
      // 針對遍歷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) {
      // 將保存array的zval設置為is_ref
      Z_SET_ISREF_PP(array_ptr_ptr);
      }
      }
      array_ptr = *array_ptr_ptr;
      Z_ADDREF_P(array_ptr);
      }
      } else {
      ……
      }
      ……
      }


      問題2中已經分析了一部分FE_RESET的實現。這里需要特別注意,本例foreach獲取值采用了引用,因此在執行的時候FE_RESET中會進入與上題不同的另一個分支。
      最終,FE_RESET會將array的is_ref設置為true,此時內存中只有一份array的數據。
      接下來分析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);
      ……

      // 變量分離,由于此時CV中的變量本身就是一個引用,此處不會copy一份新的array
      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僅僅分離is_ref=false的變量。由于之前array已經被設置了is_ref=true,因此它不會被拷貝一份副本。換句話說,此時內存中依然只有一份array數據。

      06.png

      上圖解釋了前2次循環為何會輸出1=>b 2=>C。在第3次循環FE_FETCH的時候,將指針繼續向前移動。

      復制代碼 代碼如下:
      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;
      }


      由于此時內部指針已經指向了數組的最后一個元素,因此再向前移動會指向NULL。將內部指針指向NULL之后,我們再對數組調用key和current,則分別會返回NULL和false,表示調用失敗,此時是echo不出字符的。
      問題4:

      復制代碼 代碼如下:
      $arr = array(1, 2, 3);
      $tmp = $arr;
      foreach($tmp as $k => &$v){
      $v *= 2;
      }
      var_dump($arr, $tmp); // 打印什么?


      該題與foreach關系不大,不過既然涉及到了foreach,就一起拿來討論吧:)
      代碼里首先創建了數組$arr,隨后將該數組賦給了$tmp,在接下來的foreach循環中,對$v進行修改會作用于數組$tmp上,但是卻并不作用到$arr。
      為什么呢?
      這是由于在php中,賦值運算是將一個變量的值拷貝到另一個變量中,因此修改其中一個,并不會影響到另一個。
      題外話:這并不適用于object類型,從PHP5起,對象的便總是默認通過引用進行賦值,舉例來說:

      復制代碼 代碼如下:
      class A{
      public $foo = 1;
      }
      $a1 = $a2 = new A;
      $a1->foo=100;
      echo $a2->foo; // 輸出100,$a1與$a2其實為同一個對象的引用


      回到題目中的代碼,現在我們可以確定$tmp=$arr其實是值拷貝,整個$arr數組會被再復制一份給$tmp。理論上講,賦值語句執行完畢之后,內存中會有2份一樣的數組。
      也許有同學會疑問,如果數組很大,豈不是這種操作會很慢?
      幸好php有更聰明的處理辦法。實際上,當$tmp=$arr執行之后,內存中依然只有一份array。查看php源碼中的zend_assign_to_variable實現(摘自php5.3.26):

      復制代碼 代碼如下:
      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;
      ……
      // 左值為object類型
      if (Z_TYPE_P(variable_ptr) == IS_OBJECT && Z_OBJ_HANDLER_P(variable_ptr, set)) {
      ……
      }
      // 左值為引用的情況
      if (PZVAL_IS_REF(variable_ptr)) {
      ……
      } else {
      // 左值refcount__gc=1的情況
      if (Z_DELREF_P(variable_ptr)==0) {
      ……
      } else {
      GC_ZVAL_CHECK_POSSIBLE_ROOT(*variable_ptr_ptr);
      // 非臨時變量
      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會運行到這里,
      // value為指向$arr里實際array數據的指針,variable_ptr_ptr為$tmp里指向數據指針的指針
      // 僅僅是復制指針,并沒有真正拷貝實際的數組
      *variable_ptr_ptr = value;
      // value的refcount__gc值+1,本例中refcount__gc為1,Z_ADDREF_P之后為2
      Z_ADDREF_P(value);
      }
      } else {
      ……
      }
      }
      Z_UNSET_ISREF_PP(variable_ptr_ptr);
      }
      return *variable_ptr_ptr;
      }

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

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