在編寫一段析構方法的研究代碼中,我遇到了交叉知識點致使的錯誤——在不一樣做用域,析構方法與引用次數致使了不同的結果。
本文僞裝你已經明白什麼是析構方法、做用域及引用次數。關於後者,引用次數是 PHP 垃圾收集中的重要機制,它很大程度上,幫助 PHP 在程序運行時清理內存垃圾(參考:引用計數基礎 - PHPDoc)。php
來看這段代碼:html
class A { public $var = []; public function __construct() { echo '__construct: ' . spl_object_hash($this) . "\n"; } public function __destruct() { echo '__destruct: ' . spl_object_hash($this); } public function test() { throw new Exception('Hello'); } } $test = new A(); $test->test();
個人本意是「在拋出不捕獲的異常時,析構方法是否正常執行」。結果是沒有執行,OK,很穩:網絡
__construct: 0000000045f0af9e00000000494744b0 Fatal error: Uncaught Exception: Hello in...
當咱們覺得事情就此結束,後續每每會接踵而來。函數
在公司前輩指出「你這段代碼有問題,犯了做用域的錯誤」以後,我是當場宕機的。oop
啥,做用域?析構方法?我是否是聽錯了,那玩意不是變量的概念麼。學習
通過個人追問,前輩告訴我:你把執行代碼放到函數裏試試。測試
避免文章過長,直接上差別部分的代碼,以下:this
class A { // 與以前一致 } function test() { $test = new A(); $test->test(); } test();
結果以下:spa
__construct: 000000004b11d811000000006f9a75c7 __destruct: 000000004b11d811000000006f9a75c7 Fatal error: Uncaught Exception: Hello in...
心態以下:.net
說好的不執行呢?真是使人絕望。
當場打臉,只好去琢磨「析構方法的做用域」是個啥。搜索結果裏看到了這樣的話:
析構函數會在到某個對象的全部引用都被刪除或者當對象被顯式銷燬時執行。來源: 構造與析構函數 - PHPDoc
讓咱們推理一下:
結果彷佛明朗了。
固然,淺嘗輒止可對不起個人好奇心。既然要搞明白這個問題,那就問一問核心問題:
相信在理清上述兩個問題的答案後,這個文章也就沒有存在的意義了,笑~
函數級別的做用域結束與對象執行析構方法,是否有必然聯繫?
很簡單,我們讓對象與函數的做用域脫鉤,就能夠逆向地證明這一點:
class A { // 與以前一致 } $i = 123; function test(&$i) // 經過引用機制,給函數的做用域增長污染變量 { $test = new A(); $i = $test; // 將對象實例的引用擴展到全局做用域 $test->test(); } test($i);
結果以下:
__construct: 0000000042a054c3000000001f53236f Fatal error: Uncaught Exception: Hello in...
果真,當引用計數不爲 0 時,析構函數就不會被調用,賊穩~
新問題:調用析構方法與結束變量,誰先誰後?
這個問題就有點意思了,熟悉程序的朋友又應該明白,遇到這種「X的某個機制是何時觸發的」,就應該去看X的生命週期,X 泛指一切。
在通過一番查找,我從《PHP7內核剖析》中找到了 PHP 的生命週期,注意我標紅圈的兩個地方:
清理全局變量與析構方法的調用,咱們就找到了。
但此時困惑了個人問題就變成了:普通變量到底何時銷燬?
我翻遍了 PHP 的生命週期、網絡上的文章,也沒找到想要的答案。你們都在聊全局變量的銷燬事件,難道全局的普通變量是弱勢羣體嗎?
直到我看到 PHP 手冊上的範例:
// 使用 global $a = 1; $b = 2; function Sum() { global $a, $b; $b = $a + $b; } Sum(); echo $b;
原來 全局範圍的普通變量 = 全局變量,這結論真是令我頭禿。
最終總結一下:
至於爲何會犯這樣的錯誤,緣由也有兩個:
爲何會犯這兩個錯誤,天然也有理由,但不管什麼理由,都解決不了在面對知識點交叉時,由於知識盲點所犯下的錯。下次學東西,仍是跟着官方文檔學習吧。
圖片出處源自網絡或水印,侵刪。