在编写一段析构方法的研究代码中,我遇到了交叉知识点致使的错误——在不一样做用域,析构方法与引用次数致使了不同的结果。
本文伪装你已经明白什么是析构方法、做用域及引用次数。关于后者,引用次数是 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;
原来 全局范围的普通变量 = 全局变量,这结论真是令我头秃。
最终总结一下:
至于为何会犯这样的错误,缘由也有两个:
为何会犯这两个错误,天然也有理由,但不管什么理由,都解决不了在面对知识点交叉时,由于知识盲点所犯下的错。下次学东西,仍是跟着官方文档学习吧。
图片出处源自网络或水印,侵删。