PHP——2(PHP變量作用域)
PHP——3(PHP變量分離/引用(Variables Separation))
為輔助閱讀熬甫,可直接跳過
Laruence關(guān)于引用賦值行為的描述和用debug_zval_dump刺探的結(jié)果有出入栖秕,下面我會指出Laruence的敘述有誤的地方邢笙。
總結(jié)來說,分析PHP賦值行為遵循以下原則:
在保證常規(guī)賦值思維的前提下墅拭,保證占用盡量小的內(nèi)存和做盡量少的指針指向變化秒咐。
1.內(nèi)存的問題("Copy on write")
由于PHP弱類型變量的原因,在PHP中存儲一個變量會比在C等強(qiáng)類型語言中占用的內(nèi)存大得多宿亡,如果也按照C語言中每個變量開辟一個單獨(dú)的內(nèi)存空間來保存,顯然將占用大量的內(nèi)存空間纳令,顯然這并不是最優(yōu)化的設(shè)計挽荠。我們自己可以設(shè)想一下在PHP中諸如以下的賦值語句
<?php
$a=1;
$b=$a;
?>
對$a顯然要開辟一塊新的內(nèi)存克胳,但是對于$b來說要不要開辟新的內(nèi)存呢?
顯然圈匆,在PHP中我們是不需要像C一樣毯欣,每次都去開辟變量內(nèi)存的,針對上面的情況臭脓,我們只需要在變量符號表中加入名字b,同時將其對應(yīng)的zval指針指向$a指向的zval內(nèi)存地址即可腹忽。其內(nèi)存結(jié)構(gòu)變化示意圖如下
這時候沒有做內(nèi)存開辟来累,那么什么時候會做內(nèi)存開辟呢?答案是出現(xiàn)不可調(diào)和的矛盾時會產(chǎn)生內(nèi)存開辟窘奏。
我們觀察“$a賦值X”這一語句嘹锁,賦值可為普通賦值(=)和引用賦值(=&),X可為常量或變量着裹。所有的內(nèi)存開辟都是在X端完成领猾,整理列表如下。
可以看到骇扇,新建內(nèi)存只有如下三種情況:
- 名字a不存在摔竿,賦給一個常量,這時候沒辦法必須新建
- $b=&$c少孝,$a=$b继低,最開始$b和$c捆綁在一起,除非$b重新綁定到其他引用關(guān)系上稍走,$b袁翁、$c的綁定關(guān)系是不會解除的,這時候$a想和$b在一起又不想和$c綁定(如果$a=&$b婿脸,那就是a粱胜、b、c綁定了)狐树,那就只有分出去一個$b的復(fù)制品給$a了焙压,希望這樣解釋比較好理解。在程序中判斷條件為賦值行為為=且X的is_ref=1抑钟。
- $b=$c,$a=&$b冗恨,這時候$b想和$a綁定在一起,$a又不想$c攙和進(jìn)來(如果$b=&$c味赃,那就是a掀抹、b、c綁定了)心俗,那就只有為難$b分出個復(fù)制品給$c了傲武。在程序中判斷條件為賦值行為為=&且X的refcount>1蓉驹。
可以看到所有要新建內(nèi)存的地方都是矛盾不可調(diào)和的地方,這也是動態(tài)類型語言設(shè)計的高明之處揪利,也是所謂的"Copy on write"原則的意思态兴。
2.指針的問題("Change on write")
在之前我們談到的$a,$b指向同一內(nèi)存結(jié)構(gòu),實際上就是$a,$b名字對應(yīng)的指針指向統(tǒng)一內(nèi)存結(jié)構(gòu)疟位,對于如下語句
<?php
$a = 1;
$b = $a;
echo '$a='.$a."\t".'$b='.$b."\n";
$b = 2;
echo '$a='.$a."\t".'$b='.$b."\n";
$c = 1;
$d = &$c;
echo '$c='.$c."\t".'$d='.$d."\n";
$d = 2;
echo '$c='.$c."\t".'$d='.$d."\n";
?>
$a=1 $b=1
$a=1 $b=2
$c=1 $d=1
$c=2 $d=2
這里我們用一張圖來說明其內(nèi)存結(jié)構(gòu)和指針指向變化示意圖如下
這兩個例子的唯一不同在于定義$d時加上了一個引用定義符&瞻润。
為什么$b賦值時指向發(fā)生了變化而$d賦值時沒有變化,是么時候指針指向才會發(fā)生變化呢?答案是同樣是出現(xiàn)不可調(diào)和的矛盾時會產(chǎn)生指針指向改變甜刻。
同樣我們觀察“$a賦值X”這一語句绍撞,賦值可為普通賦值(=)和引用賦值(=&),X可為常量或變量得院。所有的指針指向改變都是在$a端完成傻铣。
這里我就不再列表了,可以同樣按照以上方法分析祥绞,結(jié)論同樣是三種情況下指針指向發(fā)生改變:
- 名字a不存在非洲,這時候新建了名字沒辦法必須將指針指向一個地方
- $b=1,$a=$b蜕径,$a=2两踏,最開始$a和$b指向同一內(nèi)存結(jié)構(gòu),當(dāng)$a要指向一個新的值又不希望$c指向這個值兜喻,那么只有將$a指針從原來的指向$b內(nèi)存結(jié)構(gòu)改為指向新建的內(nèi)存結(jié)構(gòu)缆瓣。在程序中判斷條件為賦值行為為=且$a的refcount>1。
- $b=1虹统,$c=2弓坞,$a=&$b,$a=&$c车荔,這時候$a想和$c綁定在一起渡冻,只能綁定一個內(nèi)存結(jié)構(gòu),$a只有舍棄$b忧便,將$a的指針從原來指向$b內(nèi)存結(jié)構(gòu)改為指向$c內(nèi)存結(jié)構(gòu)族吻。在程序中判斷條件為賦值行為為=&且$a的is_ref=1。
可以看到所有要改變指針指向的地方同樣是矛盾不可調(diào)和的地方珠增,這同樣也是動態(tài)類型語言設(shè)計的高明之處超歌,也是所謂的"Change on write"原則的意思。
實際上所有弱類型的語言機(jī)理大概都是如此蒂教,只有按照這樣的邏輯來設(shè)計的動態(tài)語言才是最優(yōu)性能同時不反人類的巍举,下一節(jié)深入到PHP的存儲結(jié)構(gòu)來講解PHP的引用,說明PHP引用特別是對象引用容易被錯誤理解的地方凝垛。