1. 程式人生 > >1.深入PHP變數儲存結構

1.深入PHP變數儲存結構

首先宣告,我並沒有去讀PHP的原始碼,只是對於PHP的有時候詭異的表現感興趣,找了一下開發人員laruence的部落格結合PHP提供的函式debug_zval_dump刺探得到了本部落格所闡述的工作機理。如果你想對PHP變數儲存結構有一個瞭解或想對PHP變數加深理解的話,本文是適合你的,比較深入的去看原始碼吧。

為了保證部落格的連貫性,首先引用laruence關於PHP變數內部儲存結構的部分內容(稍作修改)

在PHP中,所有的變數都是用一個結構-zval來儲存的, 在Zend/zend.h中我們可以看到zval的定義:

  1.   typedef struct _zval_struct {
  2.     zvalue_value value;
  3.     zend_uint refcount;
  4.     zend_uchar type;
  5.     zend_uchar is_ref;
  6.   } zval;

其中zvalue_value是真正儲存資料的關鍵部分,定義為一個聯合體(union)

  1. typedef union _zvalue_value {
  2.     long lval;
  3.     double dval;
  4.     struct {
  5.         char *val;
  6.         int len;
  7.     } str;
  8.     HashTable *ht;
  9.     zend_object_value obj;
  10. } zvalue_value;

PHP中常見的變數型別有:

  1. 1. 整型/浮點/長整型/bool值 等等
  2. 2. 字串
  3. 3. 陣列/關聯陣列
  4. 4. 物件
  5. 5. 資源

PHP根據zval中的type欄位來儲存一個變數的真正型別,然後根據type來選擇如何獲取zvalue_value的值,比如對於整型和bool值:

  1.    zval.type = IS_LONG;//整形
  2.    zval.type = IS_BOOL;//布林值

就去取zval.value.lval,對於bool值來說lval∈(0|1);如果是雙精度,或者float則會去取zval.value的dval。而如果是字串,那麼:

  1.    zval.type = IS_STRING

這個時候,就會取:zval.value.str而這個也是個結構,存有C分格的字串和字串的長度。

而對於陣列和物件,則type分別對應IS_ARRAY, IS_OBJECT, 相對應的則分別取zval.value.ht和obj

比較特別的是資源,在PHP中,資源是個很特別的變數,任何不屬於PHP內建的變數型別的變數,都會被看作成資源來進行儲存,比如,資料庫控制代碼,開啟的檔案控制代碼等等。 對於資源:

  1.    type = IS_RESOURCE

這個時候,會去取zval.value.lval, 此時的lval是個整型的指示器, 然後PHP會再根據這個指示器在PHP內建的一個資源列表中查詢相對應的資源,此時的lval就好像是對應於資源連結串列的偏移值。

  1.  ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type);

借用這樣的機制,PHP就實現了弱型別,因為對於ZE的來說,它所面對的永遠都是同一種類型,那就是zval。

上面部分博文只是闡明瞭PHP變數的內部表示,要想知道內部表示是如何和使用者指令碼中的變數聯絡起來的,需要看laruence的另一篇博文深入理解PHP原理之變數作用域(Scope in PHP),同樣引用部分內容(稍作修改)如下:

如果在指令碼中寫下:

  1. <?php
  2.   $var = "laruence";
  3.   echo $var;
  4. ?>

ZE是如何把我的變數var和內部結構zval聯絡起來的呢?

PHP內部都是使用zval來表示變數的,但是對於上面的指令碼,我們的變數是有名字的, var。而zval中並沒有相應的欄位來體現變數名。PHP內部一定有一個機制,來實現變數名到zval的對映。

在PHP中,所有的變數都會儲存在一個數組中(確切的說是hash table)。

當你建立一個變數的時候,PHP會為這個變數分配一個zval,填入相應的變數值,然後將這個變數的名字,和指向這個zval的指標填入一個數組中。然後,當你獲取這個變數的時候,PHP會通過查詢這個陣列,獲得對應的zval。

檢視_zend_executor_globals結構(這個結構在PHP的執行器儲存一些執行相關的上下文資訊)

  1. struct _zend_executor_globals {
  2.      ....
  3.     HashTable *active_symbol_table;/*活動符號表*/
  4.     HashTable symbol_table; /*全域性符號表*/
  5.     HashTable included_files;
  6.     jmp_buf *bailout;
  7.     int error_reporting;
  8.      .....
  9. }

總結以上兩篇部落格,對於如下程式

<?php
class class1
{
	public $member;
	
	function __construct()
	{
		$this->member = 1;
	}
}

$a = 1;
$b = 0.56;
$c = "string";
$d = array(1=>1);
$e = new class1;
$f = tmpfile();

debug_zval_dump($a);
debug_zval_dump($b);
debug_zval_dump($c);
debug_zval_dump($d);
debug_zval_dump($e);
debug_zval_dump($f);
?>

該程式使用debug_zval_dump刺探LONG、DOUBLE、STRING、ARRAY、OBJECT、RESOURCE型別變數,結果如下

long(1) refcount(2)
double(0.56) refcount(2)
string(6) "string" refcount(2)
array(1) refcount(2){
  [1]=>
  long(1) refcount(1)
}
object(class1)#1 (1) refcount(2){
  ["member"]=>
  long(1) refcount(1)
}
resource(4) of type (stream) refcount(2)

分析繪製整個儲存結構如下

對照此圖就可以知道PHP各種型別的變數在記憶體中儲存結構和使用者變數如何跟記憶體結構掛鉤,瞭解了這些是下一篇博文關於PHP引用詳解的基礎。期待下一篇PHP的賦值行為詳解吧。