上次跟大家講了垃圾回收機制後,有些小夥伴對底層原理比較感興趣,私信問我了一些關於變數的相關知識,既然大家對變數比較感興趣,那麼這次我們來系統的講一下變數的底層原理
變數結構
首先,我們還是先擺上我們的zval結構體,即php所有變數都會以zval結構體的形式實現
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} value; //變數value值
zend_uint refcount__gc; //引用計數記憶體中使用次數,為0刪除該變數
zend_uchar type; //變數型別
zend_uchar is_ref__gc; //區分是否是引用變數,是引用為1,否則為0
};
複製程式碼
從上面結構體內容可以看出每一個php變數都會由變數型別
、value值
、引用計數次數
和是否是引用變數
四部分組成
注:上面zval結構體是php5.3版本之後,php7版本之前的結構
變數型別
看到這裡,可能會有小夥伴們問我,php不是有8種資料型別嗎?但是為什麼對應的zvalue的value值只有5種?
原因是這樣的,php出於對記憶體節省的考慮,所以對於一些變數型別做了複用,並沒有一一對應去定義每個變數型別
下面我們看一下zvalue的每個value值所對應的變數型別
zval.value.lval => 整型、布林型、資源
zval.value.dval => 浮點型
zval.value.str => 字串
zval.value.*ht => 陣列
zval.value.obj => 物件
複製程式碼
看到這裡大家可能會比較奇怪,布林型和資源是怎麼對應到zval.value的lval上的呢?還有,NULL呢?
布林型
就像我們會將true和false對映成0和1進行資料庫儲存一樣,php也是這麼做的。所以php發現zval的type值是布林型時,會將布林型轉成0或1儲存在zval.value的lval中
資源
資源對於php來說屬於一個比較特殊的變數,而php會將每個資源對應的資源標識儲存在zval.value的lval中。常見的資源有:檔案控制程式碼、資料庫控制程式碼等
NULL
對於NULL來說,就更好理解了,因為本身通過zval的type值即可區分,所以並沒有將NULL值儲存在zval的value中
變數生成
php作為一門動態語言,沒有先宣告變數後賦值的習慣,所以都是拿來一個變數直接就進行了賦值,那麼是如何實現的呢?
舉例:
$name = "許錚的技術成長之路";
複製程式碼
變數容器生成
其實每次變數被常量賦值時,都會對應生成一個變數容器。剛才的例子會生成一個變數容器,容器的type是字串型別,而value值則是許錚的技術成長之路
,且此時該變數容器的ref_count會加1
變數名和變數容器關聯
而變數name
是如何與變數容器關聯起來的呢?其實也是使用了php的一個內部機制,即雜湊表
。每個變數的變數名和指向zval結構的指標被儲存在雜湊表
內,以此實現了變數名到變數容器的對映
變數作用域
上面我們提到了變數名和變數容器對映的概念。對於php來說,變數有全域性變數和區域性變數之分;那麼,他們都是儲存到一個雜湊表
內了麼?
其實不是的,變數儲存也有作用域的概念。全域性變數被儲存到了全域性符號表
內,而區域性變數也就是指函式或物件內的變數,則被儲存到了活動符號表
內(每個函式或物件都單獨維護了自己的活動符號表。活動符號表的生命週期,從函式或物件被呼叫時開始,到呼叫完成時結束)
變數銷燬
變數銷燬,分為以下幾種情況:
1、手動銷燬
2、垃圾回收機制銷燬(引用計數清0銷燬和根緩衝區滿後銷燬)
我們這次主要講一下手動銷燬,即unset,每次銷燬時都會將符號表內的變數名和對應的zval結構進行銷燬,並將對應的記憶體歸還到php所維護的記憶體池內(按記憶體大小劃分到對應記憶體列表中)
而對於垃圾回收機制的銷燬,如果你不瞭解其相關原理,那麼我建議你看下我之前寫的文章php底層原理之垃圾回收機制
思考
今天,我們從底層的角度,將變數從生成到銷燬講了一遍。對於變數的生成,我們是拿常量賦值作為示例講解的,那麼變數之間的賦值呢?是什麼原理呢?且聽下回分解~