程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 網頁編程 >> PHP編程 >> 關於PHP編程 >> 變量改變時PHP內核做了些什麼?

變量改變時PHP內核做了些什麼?

編輯:關於PHP編程

變量改變時PHP內核做了些什麼?


引言

內容來自於《Extending and Embedding PHP》- Chaper 3 - Memory Management,加上自己的理解,對php中變量的引用計數、寫時復制,寫時改變,寫時復制和改變做個”翻譯“。

zval

看下面的內容之前先對zval這個結構體做個了解

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount;
    zend_uchar type;
    zend_uchar is_ref;
} zval;

zval結構體中共有4個元素,value是一個聯合體,用來真正的存儲zval的值,refcount用來計數該zval被多少個變量使用,type表示zval所存儲的數據類型,is_ref用來標志該zval是否被引用。

引用計數

<?php
    $a = 'Hello World';
    $b = $a;
    unset($a);
?>

我們一起來剖析下上面這段代碼:

  • $a = 'Hello World';首先這句代碼被執行,內核創建一個變量,並分配12字節的內存去存儲字符串'Hello World'和末尾的NULL。
  • $b = $a;接著執行這句代碼,執行這句的時候內核裡面發生了什麼呢?

    • $a所指向的zval中的refcount進行加1操作。
    • 將變量$b指向$a所指向的zval。
      在內核中大概是這樣的,其中active_symbol_table是當前的變量符號表

          {
              zval *helloval;
              MAKE_STD_ZVAL(helloval);
              ZVAL_STRING(helloval, "Hello World", 1);
              zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),
                                                  &helloval, sizeof(zval*), NULL);
              ZVAL_ADDREF(helloval);
              zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),
                                                  &helloval, sizeof(zval*), NULL);
          }
  • unset($a);這句代碼執行後,內核會將a對應的zval結構體中的refcount計數減一,b還和原來一樣

寫時復制

<?php
    $a = 1;
    $b = $a;
    $b += 5;
?>

上面這段代碼執行完之後,一般肯定希望$a=1,$b=6,但是如果像引用計數那樣,$a$b指向相同的zval,修改$b之後$a不是也變了?
這個具體是怎麼實現的呢,我們一起來看下:

  • $a = 1;內核創建一個zval,並分配4個字節存儲數字1。
  • $b = $a;這一步和引用計數中的第二步一樣,將$b指向和$a相同的zval,並將zval中的引用計數值refcount加1。
  • $b += 5;關鍵是這一步,這一步驟發生了什麼呢,怎麼確保修改之後不影響$a

    • 其實Zend內核在改變zval之前都會去進行get_var_and_separete操作,如果recfount>1,就需要分離就創建新的zval返回,否則直接返回變量所指向的zval,下面看看如何分離產生新的zval。
    • 復制一個和$b所指向zval一樣的zval。
    • $b所指向的zval中的refcount計數減1。
    • 初始化生成的新zval,設置refcount=1,is_ref=0。
    • $b指向新生成的zval。
    • 對新生成的zval進行操作,這就是寫時復制。
      下面看看內核中分離時的主要代碼:

          zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
          {
              zval **varval, *varcopy;
              if (zend_hash_find(EG(active_symbol_table),
                              varname, varname_len + 1, (void**)&varval) == FAILURE) {
              /* Variable doesn't actually exist  fail out */
              return NULL;
          }
          if ((*varval)->is_ref || (*varval)->refcount < 2) {
              /* varname is the only actual reference,
              * or it's a full reference to other variables
              * either way: no separating to be done
              */
              return *varval;
          }
          /* Otherwise, make a copy of the zval* value */
          MAKE_STD_ZVAL(varcopy);
          varcopy = *varval;
          /* Duplicate any allocated structures within the zval* */
          zval_copy_ctor(varcopy);
      
          /* Remove the old version of varname
          * This will decrease the refcount of varval in the process
          */
          zend_hash_del(EG(active_symbol_table), varname, varname_len + 1);
      
          /* Initialize the reference count of the
          * newly created value and attach it to
          * the varname variable
          */
          varcopy->refcount = 1;
          varcopy->is_ref = 0;
          zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,
                                                  &varcopy, sizeof(zval*), NULL);
          /* Return the new zval* */
          return varcopy;
          }

寫時改變

<?php
    $a = 1;
    $b = &$a;
    $b += 5;
?>

上面這段代碼執行完之後一般希望是:$a == $b == 6。這個又是怎麼實現的呢?

  • $a = 1;這一步驟和寫時復制中的第一步一樣。
  • $b = &$a;這一步驟內核會將$b指向$a所指向的zval,將zval中的refcount加1,並將zval中的is_ref置為1。
  • $b += 5;這一步驟和寫時復制中的第三步驟一樣,但是內核中發生的事情卻不一樣。

    • 內核看到$b進行變化的時候,也會執行get_var_and_separate函數,看是否需要分離。
    • 如果(*varval)->is_ref的話也會直接返回$b所指向的zval,不去分離產生新的zval,不管zval的refcount是否>1。
    • 這時候再去修改$b值,$a的值也就改變了,因為他們指向相同的zval。

分離的問題

說道現在聰明的你可能已經看出點問題了,如果一個zval結構體既有refcount計數又有is_ref引用這個時候怎麼辦?

<?php
    $a = 1;
    $b = $a;
    $c = &$a;
?>

如果出現上面這種情況的時候,如果$a、$b、$c指向同一個zval結構體,進行改變的時候Zend到底去聽誰的?其實這個地方不會指向同一個zval了。
如果對一個is_ref = 0 && refcount >1的zval進行寫時改變這種賦值形式(就是引用賦值)的時候,Zend會將等號右邊的變量分離出來一個新的zval,
對這個zval進行初始化,對之前的zval的refcount進行減1操作,讓等號左邊的變量指向這個新的zval,refcount進行加1操作,is_ref=1。看看下面這張圖片

<?php
    $a = 1;
    $b = &$a;
    $c = $a;
?>

上面這又是另外一種情況,在is_ref = 1的情況下,試圖單純的進行refcount+1操作的時候會分離出來一個新的zval給等號左邊的變量,並初始化他,看看下面這張圖片

參考文獻

1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved