摘要:在編寫一段析構方法的研究代碼中,我遇到了交叉知識點導致的錯誤在不同作用域,析構方法與引用次數導致了不一樣的結果。前提本文假裝你已經明白什么是析構方法作用域及引用次數。當析構函數的調用鉤子去檢測引用數時,全局的實例自然無法觸發這個事件。
在編寫一段析構方法的研究代碼中,我遇到了交叉知識點導致的錯誤——在不同作用域,析構方法與引用次數導致了不一樣的結果。前提
本文假裝你已經明白什么是析構方法、作用域及引用次數。關于后者,引用次數是 PHP 垃圾收集中的重要機制,它很大程度上,幫助 PHP 在程序運行時清理內存垃圾(參考:引用計數基礎 - PHPDoc)。
正文 有誤的測試來看這段代碼:
class A { public $var = []; public function __construct() { echo "__construct: " . spl_object_hash($this) . " "; } 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...
當我們以為事情就此結束,后續往往會接踵而來。
翻車的代碼在公司前輩指出“你這段代碼有問題,犯了作用域的錯誤”之后,我是當場宕機的。
啥,作用域?析構方法?我是不是聽錯了,那玩意不是變量的概念么。
經過我的追問,前輩告訴我:你把執行代碼放到函數里試試。
避免文章過長,直接上差異部分的代碼,如下:
class A { // 與之前一致 } function test() { $test = new A(); $test->test(); } test();
結果如下:
__construct: 000000004b11d811000000006f9a75c7 __destruct: 000000004b11d811000000006f9a75c7 Fatal error: Uncaught Exception: Hello in...
心態如下:
說好的不執行呢?真是令人絕望。
當場打臉,只好去琢磨“析構方法的作用域”是個啥。搜索結果里看到了這樣的話:
析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行。來源:構造與析構函數 - 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;
原來 全局范圍的普通變量 = 全局變量,這結論真是令我頭禿。
最終總結一下:
當實例的引用為 0 時,會步入銷毀階段,此時,析構函數才會啟用;
當對象的實例位于全局作用域,該變量會在 全局變量銷毀 事件中銷毀,在此之前,全局變量的引用數至少為 1;
析構函數的調用 發生在 全局變量銷毀 之前。
當析構函數的調用鉤子去檢測“引用數”時,全局的實例自然無法觸發這個事件。
至于為什么會犯這樣的錯誤,原因也有兩個:
對 PHP 的生命周期認知模糊不清;
不清楚 PHP 的全局變量如何定義。
為什么會犯這兩個錯誤,自然也有理由,但無論什么理由,都解決不了在面對知識點交叉時,因為知識盲點所犯下的錯。下次學東西,還是跟著官方文檔學習吧。
圖片出處源自網絡或水印,侵刪。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/29766.html
摘要:二面向對象有什么特征面向對象的主要特征有抽象繼承封裝和多態。析構函數析構函數是在引入的,它的作用與調用時機和構造函數剛好相反,它在對象被銷毀時自動執行。 PHP面試專欄正式起更,每周一、三、五更新,提供最好最優質的PHP面試內容。PHP中面向對象常考的知識點有以下7點,我將會從以下幾點進行詳細介紹說明,幫助你更好的應對PHP面試常考的面向對象相關的知識點和考題。整個面向對象文章的結構涉...
摘要:通過注冊自動加載器,腳本引擎在出錯失敗前有了最后一個機會加載所需的類構造函數和析構函數構造函數允行開發者在一個類中定義一個方法作為構造函數。析構函數會在到某個對象的所有引用都被刪除或者當對象被顯式銷毀時執行抽象類支持抽象類和抽象方法。 語言參考 1. 類型 1.1 簡介 showImg(https://segmentfault.com/img/bVbqlNJ?w=531&h=379);...
摘要:類與對象基本概念如果在之后跟著的是一個包含有類名的字符串,則該類的一個實例被創建。如果該類屬于一個名字空間,則必須使用其完整名稱。如果一個類被聲明為,則不能被繼承。命名空間通過關鍵字來聲明。 類與對象 基本概念 new:如果在 new 之后跟著的是一個包含有類名的字符串,則該類的一個實例被創建。如果該類屬于一個名字空間,則必須使用其完整名稱。 Example #3 創建一個實例 ...
摘要:繼上一篇面試常考內容之面向對象發表后,今天更新,需要的可以直接點擊文字進行跳轉獲取。析構函數,當對象被銷毀時調用。 PHP面試專欄正式起更,每周一、三、五更新,提供最好最優質的PHP面試內容。繼上一篇PHP面試常考內容之面向對象(1)發表后,今天更新(2),需要(1)的可以直接點擊文字進行跳轉獲取。整個面向對象文章的結構涉及的內容模塊有: 一、面向對象與面向過程有什么區別?二、面向對...
閱讀 3027·2021-11-02 14:40
閱讀 844·2019-08-30 15:53
閱讀 1265·2019-08-30 15:53
閱讀 3259·2019-08-30 13:53
閱讀 3305·2019-08-29 12:50
閱讀 1132·2019-08-26 13:49
閱讀 1863·2019-08-26 12:20
閱讀 3660·2019-08-26 11:33