在前面的文章《深入理解php原理之變數結構》中我已經介紹了php變數的內部結構,下面我將會對變數賦值過程中,php內部對資料處理的原理進行闡述,不過在講述該原理前,需要先了解一下變數名和它的值是如何關聯起來的,這個對變數賦值內部原理的理解非常重要,例如:
<?php
$a = 1;
?>
這個例子看起來非常簡單,但是你知道「變數名a」和「數值1」是怎樣關聯起來的麼?在《深入理解php原理之變數結構》一文中已經講過,php中所有的值都是通過結構體zval儲存的,大家如果對zval不太清楚,建議先看看這篇博文,下面讓我們先回顧一下zval:
struct _zval_struct zval;
有了數值zval和變數名a,下面就是如何將兩者關聯的問題了。因為變數名a其實有乙個指標ptr_a,每次初始化乙個變數時,系統會先開闢一塊記憶體,將變數的值保持在zval中,然後將變數a和對應的指標ptr_a保持在數值中,同時讓ptr_a指向zval的首位址,如下圖所示,是不是很簡單呢,哈哈:)
知道變數名和它的值如何關聯後,我們就可以開始深入了解變數賦值原理,為了方便大家理解,我將通過**的方式來講解。對於變數賦值原理,《深入理解php核心》中將該部分內容分為5種情況,所以我也通過這5個部分進行講解,不過對於裡面有些**的講解,感覺有點問題,在此就不多提了哈,開始切入正題。
情況1:賦值的左值is_ref=0,左值的refcount為1,左值==右值
<?php
$a = 1;
$a = $a;
?>
這種情況比較簡單,refcount前後沒有變化,不過在核心中,refcount其實進行了先減1,然後加1的操作。
情況2:賦值的左值is_ref=0,左值的refcount為1,右值is_ref=0
<?php
$a = 1;
$b = $a;
?>
按照常規思維,我們可能會給變數b重新申請一塊記憶體,然後將a的值copy到自己的zval,如果真是這樣的話,感覺非常low,當然php開發者也想到了這一點,對於這種情況,完全可以讓a的指標ptr_a和b的指標ptr_b指向相同的zval,然後將引用計數refcount加1,不過這樣會有個問題,如果將變數a銷毀後,是不是zval空間會被釋放,然後變數b的值也被銷毀,或者還是通過其它的方式進行處理?(這個問題留給大家, 我就不解答了哈~~)
情況3:賦值的左值is_ref=1,左值!=右值
<?php
$a = 1;
$b = &$a;
$b = 2;
?>
這裡我們可以看到,a是通過引用的方式給b賦值,當給b賦值為2時,a的值其實也變為2,那麼php核心是怎麼處理的呢?方法其實同「情況2」一樣,唯一的區別就是將是否引用is_ref賦值為1,至於這個is_rel有啥作用,後面的情況會用到。然後,當給變數b賦值為2時,php核心會進行copy on write(寫時複製)操作,即將zval中的值修改為2。(這麼理解比較簡單,其實源**的處理過程要稍微複雜些,php核心首先會將整形2的zval copy過來,然後修改refcount和is_ref變數值,最後釋放整形2的zval記憶體)
情況4:賦值的左值is_ref=0,左值的refcount為1,右值is_ref=1
<?php
$a = 1;
$b = &$a;
$c = $a;
?>
前兩步在之前已經說明,這裡就不再贅述,在第3步中,將變數a賦值給變數c,但是a是個引用,大家可以和「情況2」對比一下,這兩種情況比較像,如果按照「情況2」的處理方式,讓ptr_c指向a的zval,那麼這樣和c=&a又有什麼區別呢?所以我們只能採取變數分離的策略,即給c單獨開闢一塊zval記憶體,然後將a的zval copy過來,這樣c和a也就沒有任何關係,所以c的refcount=1,該過程我們也稱為change on write(寫時改變)。
情況5:賦值的左值is_ref=0,左值refcount!=1,右值is_ref=1,右值refcount>0
<?php
$a = 1;
$b = 2;
$c = &$b;
$a = $c;
?>
這種情況比較複雜,前3步都比較好理解,但到第4步的時候,因為b和c屬於引用關係,也可以理解為「等價」關係,然後a也有自己的zval,當第四步a=c時,不可能讓c的ptr_c直接指向和a相同的zval,其實這裡和「情況4」的處理方式幾乎一樣,唯一的區別是「情況4」是直接新開闢一塊zval記憶體,而「情況5」並沒有新開闢記憶體,而是將當前的zval中的值1修改為2。
後記:這是我結合《深入理解php核心》一書和鳥哥的部落格寫的,將兩種總結後,然後通過比較影象化的語言來描述,鳥哥的部落格寫的很生動的,但是有些情況沒有覆蓋,然後有些**層次的東西沒有講出來,《深入理解php核心》書中,有些地方沒有講清楚,然後感覺有些**寫的有問題(不知道是真有問題,還是自己的理解錯誤,反正我是推敲了好長時間,後續我會查閱php原始碼),並且對於這5種情況,排序有點亂,所以我將它們重新歸類了一下。至於這些圖,是我在理解該原理的過程中,腦海中所浮現的,所以就將他們畫了出來,希望對大家理解該部分內容會有所幫助。
參考:
深入理解PHP原理之變數宣告
在php中沒有對常規變數的宣告操作,如果要使用乙個變數,直接進行賦值操作即可,因為php在賦值操作的同時已經進行宣告操作,那麼php是怎樣在賦值前進行宣告的呢?在博文 深入理解php原理之變數賦值 中其實已經提到過變數的宣告,但是講述的不夠透徹,下面主要通過詞法分析 語法分析和獲取左值和右值的過程,...
深入理解PHP原理之變數作用域
php變數的內部表示是如何和使用者指令碼中的變數聯絡起來的呢?也就是說,如果我在指令碼中寫下 var laruence echo var ze是如何把我的變數var和內部結構zval聯絡起來的呢?深入理解php原理之變數中講過,php內部都是使用zval來表示變數的,但是對於上面的指令碼,我們的變數...
深入淺出理解PHP原理之變數賦值
php的變數賦值 這個標題估計很多人會不屑一顧,變數賦值?excuse me?我們學開發的第一課就會了好不好。但是,就是這樣基礎的東西,反而會讓很多人矇圈,比如,值和引用的關係。今天,我們就來具體講講。首先,定義變數和賦值這個不用多說了吧 a 1 b 2 c 4,5,6 d new stdclass...