【原创】用底层源码说话:从PHP内核角度还原下函数debug_zval_dump()看似奇怪的输出结果背后的真相

blogdaren 2021-08-27 抢沙发 75人次

问题背景:

<?php 
/*
 * 期望中的运行结果:
 *
 * string(15) "time:1630047427" refcount(1)
 * string(15) "time:1630047427" refcount(3)
 * string(15) "time:1630047427" refcount(2)
 *
 * 结果则是看似有些费解的运行结果:
 *
 * string(15) "time:1630047427" refcount(2)
 * string(15) "time:1630047427" refcount(4)
 * string(15) "time:1630047427" refcount(3)
 *
 */
$a = "time:" . time(); //$a       ->  zend_string_1(refcount = 1)
debug_zval_dump($a);   //按理说这里应该是refcount = 1,为什么refcount = 2?
$b = $a;               //$a,$b    ->  zend_string_1(refcount = 2)
$c = $b;               //$a,$b,$c ->  zend_string_1(refcount = 3)
debug_zval_dump($a);   //按理说这里应该是refcount = 3,为什么refcount = 4? 
unset($b);             //$a,$c ->  zend_string_1(refcount=2)
debug_zval_dump($c);   //按理说这里应该是refcount = 2,为什么refcount = 3? 

问题原因:

首先两个答案都是正确的,之所以输出结果存在差异,原因是: 

调用debug_zval_dump()时又发生了一次引用传参【opcode: ZEND_SEND_VAR】, 所以该函数执行期间引用计数是要+1的,执行完后执行栈以及参数会释放掉,引用计数又-1;另外的一个关键问题在于:这个函数的输出行为是发生在函数运行期间,所以如果从纯gdb角度调试来看问题的话,预期结果更接近我们的自然理解。

问题剖析:

作者将基于底层源码、从PHP内核的角度来跟踪调试还原下这个问题的真相,视频剖析链接如下:

https://www.bilibili.com/video/BV1Tf4y1H7E7/

PHP官方对此解读:

心细的小伙伴不难发现PHP官方手册对该函数的输出已做特殊声明,其中第一段声明如下:

The refcount value shown by this function may be surprising without a detailed understanding of the engine's implementation.

翻译下即:如果对Zend引擎的实现没有深入的理解的话,那么开发者将会对这个函数返回的引用计数感到惊讶。

官方手册完整版摘录过来自己品吧:

Note: Understanding the refcount
The refcount value shown by this function may be surprising without a detailed understanding of the engine's implementation.

The Zend Engine uses reference counting for two different purposes:

Optimizing memory usage using a technique called "copy on write", where multiple variables holding the same value point to the same copy in memory. When any of the variables is modified, it is pointed to a new copy in memory, and the reference count on the original is decreased by 1.
Tracking variables which have been assigned or passed by reference (see References Explained). This refcount is stored on a separate reference zval, pointing to the zval for the current value. This additional zval is not currently shown by debug_zval_dump().
Because debug_zval_dump() takes its input as normal parameters, passed by value, the copy on write technique will be used to pass them: rather than copying the data, the refcount will be increased by one for the lifetime of the function call. If the function modified the parameter after receiving it, then a copy would be made; since it does not, it will show a refcount one higher than in the calling scope.

The parameter passing also prevents debug_zval_dump() showing variables which have been assigned by reference. To illustrate, consider a slightly modified version of the above example:

版权声明:除非注明,本文由( blogdaren )原创,转载请保留文章出处。

本文链接:【原创】用底层源码说话:从PHP内核角度还原下函数debug_zval_dump()看似奇怪的输出结果背后的真相

发表评论:

您的昵称:
电子邮件:
个人主页:

Free Web Hosting