php底層原理之變數(二)

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

上週我們從底層的角度介紹了php變數從生成->常量賦值->銷燬的完整生命週期(不瞭解的同學可以翻看一下前面的文章php底層原理之變數(一)),但是我們留了一個思考,不知道大家有答案了沒,變數之間的賦值在底層又是如何實現的呢?

變數之間賦值

php變數的zval結構,我們已經介紹了很多遍了,這裡我們就不再多作介紹了。但是對於zval結構體中的refcount__gcis_ref__gc欄位我們一直都沒有詳細介紹過,而這兩個欄位其實是和變數之間賦值的原理有著密切的關係的。所以,我們這次從幾個例子入手,瞭解這兩個欄位的變化和由此帶來的原理知識

寫時複製原理

舉例:

$a = "許錚的技術成長之路";
$b = $a;
xdebug_debug_zval("a", "b");
複製程式碼

結果:

a: (refcount=2, is_ref=0)='許錚的技術成長之路'
b: (refcount=2, is_ref=0)='許錚的技術成長之路'
複製程式碼

看到這裡,大家可能會比較蒙。不是變數賦值了麼?應該發生值拷貝了呀?怎麼兩個變數的引用計數不是1,而是2呢?

那是因為,php在設計的時候,為了節省記憶體,所以在變數之間賦值時,對於值相同的兩個變數,會共用一塊記憶體,也就是會在全域性符號表內將變數b的變數指標指向變數a指向的同一個zval結構體,而只有當變數的zval結構發生變化時,才會發生變數容器複製的記憶體變化,也因此叫做寫時複製原理

那什麼時候會發生寫時複製原理呢?

寫時複製原理觸發時機:
php在修改一個變數時,如果發現變數的refcount>1,則會執行變數容器的記憶體複製

舉例:

$a = "許錚的技術成長之路";
$b = $a; //此時變數a和變數b共同指向同一個變數容器,即refcount>1
$b = "許錚的技術成長之路1" //觸發寫時複製機制
xdebug_debug_zval("a", "b");
複製程式碼

結果:

a: (refcount=1, is_ref=0)='許錚的技術成長之路'
b: (refcount=1, is_ref=0)='許錚的技術成長之路1'
複製程式碼

寫時改變原理

變數之間的賦值我們搞清楚了,那麼變數和引用之間的賦值呢?我們還是通過舉例來說明

舉例:

$a = "許錚的技術成長之路";
$b = &$a;
xdebug_debug_zval("a", "b");
複製程式碼

結果:

a: (refcount=2, is_ref=1)='許錚的技術成長之路'
b: (refcount=2, is_ref=1)='許錚的技術成長之路'
複製程式碼

此時,我們發現,變數a和b的refcount還是2,只不過is_ref變成了1,那是因為在將變數a引用賦值給變數b時,在原變數容器上作了修改,將is_ref變成了1,且refcount+1

那如果引用賦值的基礎上又發生了變數的改變了呢?

舉例:

$a = "許錚的技術成長之路";
$b = &$a;
$b = "許錚的技術成長之路1"
xdebug_debug_zval("a", "b");
複製程式碼

結果:

a: (refcount=2, is_ref=1)='許錚的技術成長之路1'
b: (refcount=2, is_ref=1)='許錚的技術成長之路1'
複製程式碼

是不是覺得很神奇?變數b和變數a的值一起發生改變了~其實這是因為觸發了寫時改變原理

寫時改變原理觸發時機:
is_ref為1的變數容器在被賦值之前,優先檢查變數容器的is_ref是否等於1,如果為1,則不進行寫時複製,而是在原變數容器基礎上作內容修改;而如果將is_ref為1的變數容器賦值給其他變數時,則會立即觸發寫時複製

那麼如果把剛剛舉得幾個例子合併在一起呢?最後結果又是什麼呢?

舉例:

$a = "許錚的技術成長之路";
$b = $a; 
$c = &$a;
xdebug_debug_zval("a", "b", "c");
複製程式碼

結果:

a: (refcount=2, is_ref=1)='許錚的技術成長之路'
b: (refcount=1, is_ref=0)='許錚的技術成長之路'
c: (refcount=2, is_ref=1)='許錚的技術成長之路'
複製程式碼

整體執行過程是這樣的,當執行到第二行時,變數容器的refcount會變成2,變數a和變數b共享同一個變數容器;當執行到第三行時,因為將變數a的引用賦值給變數c,但是變數b和變數a已經共享了同一個變數容器,此時變數容器如果要發生改變,因為refcount>2,所以會發生寫時複製,將變數a和變數b分離,之後將變數a引用賦值給變數c時,則會原基礎上進行修改,is_ref變成1,且refcount變成2

思考

那麼,下面的這個例子,最終結果是什麼呢?歡迎大家在下方留言或私信我~

舉例:

$a = "許錚的技術成長之路";
$b = $a; 
$c = &$a;
$d = $a;
$e = "許錚的技術成長之路1"
$a = $e;
xdebug_debug_zval("a", "b", "c", "d", "e");
複製程式碼

如果你喜歡我的文章,請點贊支援我下,並歡迎關注我的專欄,每週都會有原創且有深度的文章奉上喲~

相關文章