php底層原理之變數(一)

許錚的成長之路發表於2019-03-17

上次跟大家講了垃圾回收機制後,有些小夥伴對底層原理比較感興趣,私信問我了一些關於變數的相關知識,既然大家對變數比較感興趣,那麼這次我們來系統的講一下變數的底層原理

變數結構

首先,我們還是先擺上我們的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底層原理之垃圾回收機制

思考

今天,我們從底層的角度,將變數從生成到銷燬講了一遍。對於變數的生成,我們是拿常量賦值作為示例講解的,那麼變數之間的賦值呢?是什麼原理呢?且聽下回分解~

相關文章