總所周知,php與其他指令碼語言一樣屬於弱變數型別的語言。同時php本身也是通過c語言來實現。本文主要介紹php內部是如何實現弱變數型別的,並且據此分析在php開發中的需要注意的一些使用技術。其中會重點分析php中的copy on write機制和引用相關方面的話題。本章節屬於深入《深入php使用技巧》的第一部分。
在了解php實現弱變數之前,可以先思考下:如何通過c/c++來實現弱變數型別的效果呢?
這個問題我再bit培訓課上基本上有兩種答案:
方法1:採用c++的繼承機制。首先定義乙個基礎型別
class
var;
然後基於var,派生出不同的子型別intvar/floatvar/stringvar等等。
方法2:基於c語言的struct。其中乙個字段用於標識型別,另外乙個字段用於儲存資料,由於資料要是各種型別,所以通常需要採用指標,比如:
struct
var ;
兩種思路本身並沒有太大區別,也都基本上能夠滿足需求。在php中採用了第二種思路,並且做了比較多的優化。在php中,所有變數都會對應同一種型別zval,其中zval也就是struct_zval_struct,具體定義如下:
typedef
union _zvalue_value str;
hashtable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct ;
從zval可以看出,php在細節方面的確做了不少優化的功夫。
1 zend_uchar type。採用uchar節省記憶體。
2 zvalue_value value。採用union類替換void ,這樣能節省空間,並且比void 更能表義清晰。
3 在字串型別中,預設保留了字串的長度。這樣很容易做到字串的二進位制安全,並且在計算字串長度的時候不需要進行掃瞄。
觀察php弱變數的實現,也會有一下疑惑:
1 為什麼沒有int型別呢?其實在php中是由的,只是說預設int資料就儲存在long中。
2 資源型別咋表現的呢?資源在php內部其實就是一數字。詳細後續會介紹。
3 refcount和is_ref是幹嘛的呢?
php和其他語言型別,在其語法中有兩種賦值方式:引用賦值和非引用賦值(普通的=賦值)。
<?php
$a = 1;
$b = $a; // 非引用賦值
$c = &$a; // 引用賦值
?>
引用賦值和非引用賦值在php內部是如何實現的呢?一種通常的認識是:「引用賦值就是兩個變數對應同乙個zval,非引用賦值則是直接產生乙個新的zval,同時把對應的值直接copy過來。」
也就是該**的記憶體結構如下:
(該圖時大多人認為的php記憶體結構,是錯誤的)
這樣的確能夠滿足大部分情況下的需求,但顯然不是最佳解決方案,尤其在記憶體管理上,比如說一下**就會顯得非常的低效。
<?php
$arr = array(...); // 定義乙個非常大的php陣列
myfunc($arr); // 每乙個函式呼叫都試一次**的非引用賦值
?>
因為每次函式呼叫會進行一次記憶體dump,而大記憶體的記憶體dump非常耗cpu的。在c語言中一種解決方案是採用指標,所有函式呼叫盡量傳遞指標。的確很靈活高效,但也很難維護。指標可以說是c語言程式設計師心頭的痛。還有一種更高階更有效的方法時採用引用計數(reference counting)。
在php中,也可以採用引用來解決這樣的問題,但你見過採用在php中大量使用引用的嗎?顯然很少。
在php核心中,zval的實現正是採用了引用計數的概念,說起引用計數就不得不談到copy-on-write 機制。這樣前面談到的refcount和is_ref就有作用了。
refcount:引用次數。在zval初始建立的時候就為1。每增加乙個引用,則refcount ++。
is_ref:用於表示乙個zval是否是引用狀態。zval初始化的情況下會是0,表示不是引用。
在zend/zend.h內部有一些關於zval的巨集定義,裡面比較清晰的解析了引用計數的一些規則,其中重點關注以下幾個巨集定義
#define init_pzval(z) \\
(z)->refcount =
1; \\
(z)->is_ref =
0;#define separate_zval_if_not_ref(ppzv) \\
//非引用下的變數分離
if (!pzval_is_ref(*ppzv))
#define separate_zval_to_make_is_ref(ppzv) \\
//非引用下的變數分離,並且設定引用
if (!pzval_is_ref(*ppzv))
#define separate_arg_if_ref(varptr) \\
//引用下的變數分離
if (pzval_is_ref(varptr)) else
這裡面談到兩個重要的概念:
1、非引用下的變數分離。
非引用下的變數分離,是指在一堆非引用變數中插入引用的情況下,在php內部進行的一種記憶體操作。以下面的列子來看:
$a = 1;
$b = $a;
$c = &$b;
在前兩句執行之後,記憶體結構如下圖
在第三句$c = &$b
;語句中則會執行「非引用下的變數分離。」,具體步驟是:
將b分離出來,同時把a對應的zval的refcount-1。
copy 出乙個新的zval,並把zval的is_ref設定成1.
把c指向這個新的zval,同時refcount ++
2、引用下的變數分離。
引用下的變數分離,是指在一堆引用變數中進行乙個非引用賦值操作,這個時候會直接執行copy記憶體的操作。
以下面的例子來說
$a = 1;
$b = &$a;
$c = $b;
在執行完前兩行後,php中記憶體結構如下:
在第三句,則會執行「引用下的變數分離」也就是真正的copy,最終記憶體結構如下圖
據此,基本上對php變數內部的一些原理比較清楚了,但還有一些需要注意點的:
1、php變數的引用計數特性,對於陣列同樣也存在。但注意,對於key則不生效。(具體在後面章節會分析到。)
2、php變數中的物件比較特殊,在php5之後,預設都是採用引用賦值的方式。具體實現可以參考zend_objects.*系列**。
3、對於分析php內部變數,推薦採用xdebug_debug_zval,而不要採用內建的debug_zval_dump。因為php內建的debug_zval_dump函式一方面無法處理is_ref,而且採用了引用的方式來處理,從而導致看到結果會有誤解。
據此可以得出分析出不少結論:
1、在php開發中不推薦採用引用。因為php內部對記憶體優化本身做了不少工作,引用不會帶來太多優化。(但注意推薦非強制)
2、在php中strlen是o(1)的。
**:深入php使用技巧之變數
深入理解PHP原理之變數賦值
在前面的文章 深入理解php原理之變數結構 中我已經介紹了php變數的內部結構,下面我將會對變數賦值過程中,php內部對資料處理的原理進行闡述,不過在講述該原理前,需要先了解一下變數名和它的值是如何關聯起來的,這個對變數賦值內部原理的理解非常重要,例如 a 1 這個例子看起來非常簡單,但是你知道 變...
深入理解PHP原理之變數宣告
在php中沒有對常規變數的宣告操作,如果要使用乙個變數,直接進行賦值操作即可,因為php在賦值操作的同時已經進行宣告操作,那麼php是怎樣在賦值前進行宣告的呢?在博文 深入理解php原理之變數賦值 中其實已經提到過變數的宣告,但是講述的不夠透徹,下面主要通過詞法分析 語法分析和獲取左值和右值的過程,...
php學習之 變數的使用
引用 one test two one 相當於傳位址,兩個變數指向乙個位址 動態變數 one two one three two echo three.輸出 two echo three.輸出 one echo three.輸出 php中有8個型別 4種標量 int integer bool boo...