标签归档:引用计数

查看变量引用计数及是否引用的方法

查看变量引用计数及是否引用的方法

对于PHP源码阅读过程中,变量是一个非常重要的概念,更重要的是对变量的容器ZVAL理解, 如果在无法使用*nix环境进行debug,或者不想在windows环境下折腾开发环境,你可以考虑使用下面的两个方法简单的查看变量关于引用计数及是否引用的信息

debug_zval_dump函数

debug_zval_dump函数是PHP自带的标准函数。它的作用是查看一个变量在zend引擎中的引用计数、类型信息。看一个例子:

<?php
$a = 'phppan';
debug_zval_dump($a);

输出:

string(6) "phppan" refcount(2)

显示的结果与我们预期的refcount = 1不同,为什么呢?在函数参数传递时有引用计数增加操作,所以输出变量的refcount=2,因此我们在使用此函数时需要将refcount的值减去一作为真实值。

另外,debug_zval_dump支持多参数,如下代码:

<?php
$a = 'phppan';
$b = 'martin';
debug_zval_dump($a, $b);

输出:

string(6) "phppan" refcount(2) string(6) "martin" refcount(2)

由于debug_aval_dump为PHP的标准函数,则我们可以在/ext/standard/var.c中找到其定义:

PHP_FUNCTION(debug_zval_dump)

函数在实现时遍历所有传递进来的参数,调用php_debug_zval_dump函数,显示变量的引用计数和变量类型。 而在php_debug_zval_dump函数中将以变量的类型区分变量,输出相应的字符串。以 IS_LONG 为例:

 
php_printf("%slong(%ld) refcount(%u)\n", COMMON, Z_LVAL_PP(struc), Z_REFCOUNT_PP(struc));

#define Z_REFCOUNT_PP(ppz)          Z_REFCOUNT_P(*(ppz))
#define Z_REFCOUNT_P(pz)            ((pz)->refcount)

程序最终输出的refoucnt是返回的ZVAL结果中的refcount的值。

xdebug_debug_zval函数

xdebug_debug_zval函数是xdebug扩展用来显示变量类型,引用计数及是否引用等信息的函数。一个例子:

<?php
$a = 'phppan';

xdebug_debug_zval('a');

输出:

a:
(refcount=1, is_ref=0),string 'phppan' (length=6)

xdebug_debug_zval函数与PHP自带的debug_zval_dump函数相比,显示的信息更加详情具体。除了引用计数和变量类型外,它还多了is_ref和变量的长度等额外的信息。 同样,它也支持多参数。如下代码:

<?php
$a = 'phppan';
$b = 'martin';

xdebug_debug_zval('a', 'b');

输出:

a:
(refcount=1, is_ref=0),string 'phppan' (length=6)
b:
(refcount=1, is_ref=0),string 'martin' (length=6)

xdebug_debug_zval函数传递的参数是变量名,是一个字符串。

在/ext/xdebug/xdebug.c文件中我们可以找到此函数的具体实现代码。从其代码可知:它是取 EG(active_symbol_table)中的变量容器zval相关信息显示。

以扩展的方式自定义函数

上面的两种方法都是已有的函数,如果这些不能满足需求,那么自己写吧。虽然复杂一些,但是如果自己能写出来,在技术上,在对PHP的理解上会有较大的进步。 另外,tipi项目中会有相关函数。

深入理解PHP之引用一

深入理解PHP之引用一

作者Derick Rethans

译者:Martin Pan<http://www.phppan.com/>

PHP是弱语言,其变量处理的过程是不可见的。

你是否曾经很想知道在变量拷贝的时候,PHP引擎做了什么?

你是否曾经很想知道一个函数是如何以引用的方式返回一个变量?

如果是这样,请您接着向下看。

每门计算机语言都需要一些容器来保存变量数据。在一些语言当中,变量都有特定的类型,如字符串,数组,对象等等。比如C和Pascal就属于这种。而PHP则没有这样的类型。在PHP中,一个变量在某一行是字符串,可能到下一行就变成了数字。变量可以经常在不同的类型间轻易的转化,甚至是自动的转换。PHP之所以成为一个简单并且强大的语言,很大一部分的原因是它拥有弱类型的变量。但是有些时候这也会带来一些有趣的问题。

在PHP内部,变量是存储在一个叫做zval的容器中。它不仅仅包含变量的值,也包含变量的类型。Python和PHP类似,也有一个标签标记变量类型。变量容器中包含一些Zend引擎用来区分是否引用的字段。同时它也包含这个值的引用计数。

变量存储在一个相当于关联数组的符号表中。这个数组以变量名为key,并且指向包含了这些变量的容器。如图1所示:

php-variables-01

引用计数

PHP试着在变量拷贝(如 $a = $b )的时候变得聪明些。“=”也称为赋值操作符。当进行赋值操作时,Zend引擎不会创建一个新的变量窗口,而是增大变量窗口的 refcount 字段,你可以想象一下,当这个变量是一个巨大的字符串或一个巨大 的数组时,这将节约多少的内存。如图2所示,

php-variables-02

第一步: 变量a,包含文本”this is”。默认情况下,引用计数等于1

第二步:将变量$a赋值给$b和$c。这里没有新的变量容器生成,仅仅是每次在变量赋值操作时将refcount加1。因为这里执行了两次赋值操作,所以refcount最后会变成3。

现在,也许你很想知道当变量$c改变时将发生什么。根据refcount的值的不同,它会有两种不同的处理方式。如果refcount等于1,这个变量容器将更新它的值(也许同时会更新它的类型)。如果refcount大于1,将创建一个包含了新值(和类型)的变量容器。如图2所示的第三步,$a变量所在的变量容器的refcount值被减去一,现在refcount的值是2,而新创建的容器的refcount的值为1。当对一个变量使用unset函数时,这个变量所在的容器的refcount值将减去一,如图第4步所示。如果refcount的值少于1,Zend引擎将翻译这个变量容器,如图第5步所示。

传递变量给函数(Passing Variables to Functions)

除了所有脚本共用的全局符号表以外,每个用户定义的函数在调用时都会创建一个属于自己的符号表,用来存放它自己的变量。当一个函数被调用后,Zend引擎就会创建一个这样的符号表,当这个函数返回时这个函数表就会被释放。一个函数要么通过return语句返回,要么因为函数结束而返回(译者注:无返回的函数默认会返回NULL)。如图3所示:

php-variables-03

图3详细介绍了变量是如何传递给函数的。

第一步,我们将”thisis”赋给变量$a,然后我们将这个变量传递do_something()函数的$s变量。

第二步,你可以看到这与变量赋值的操作是一样的(与我们在前一小节提到的$b = $a类似),只是其存储在不同的符号表(函数符号表),并且引用计数加2,而不是加1。原因是函数栈也包含了这个变量容器的引用。

第三步,当我们赋新值给变量$s,原变量容器的refcount减1,并且创建一个包含了新值的变量容器。

第四步,我们通过return语句返回一个变量。返回的变量从全局符号表中获取一个实体并将其refcount的值增加1.当函数结束时,函数的符号表将被销毁。在销毁的过程中,Zend引擎将遍历符号表中的每个变量,并将其refcount的值减少。当变量容器的refount的值变为0,这个变量容器将会被销毁。如你所见,由于 PHP的引用计数机制,变量容器不是以拷贝的方式从函数返回。如果变量$s在第三步时没有被修改,则变量$a和$b将一直指向相同的变量容器(这个容器的refcount为2)。在这种情况下,语句$a = “this is”将不会创建变量容器的副本。

英文原文地址:http://derickrethans.nl/files/phparch-php-variables-article.pdf

以上只是第一部分的内容,原来看懂很容易,翻译出来相当难。