1、众所周知,PHP 与其他脚本语言一样,属于弱变量类型的语言。同时 PHP 本身也是通过C 语言来实现。本文 主要介绍 PHP 内部是如何实现弱变量类型的,并且据此分析在 PHP开发中需要注意的一些使用技术。其中会重点分析 PHP 中的 copy on write 机制和引用相关方面的话题。 本章节属于深入 PHP 使用技巧的第一部分。如何实现弱变量在了解 PHP 实现弱变量类型之前,可以先思考下:如何通过 C/C+来实现弱变量类型的效果呢?这个问题我在 BIT 培训课上基本上有两种答案:方法1:采用 C+的继承机制。首先定义一个基础类型Class Var然后基于 Var,派生出不同的子类型 I
2、ntVar/FloatVar/StringVar 等等。方法2:基于 C 语言的 Struct。其中一个字段用于标识类型,另外一个字段用于存储数据,由于数据要是各种类型,所以通常需要采用指针比如:struct var Int type;Void *data;两种思路本身并没有太大区别,也都基本上能够满足需求。在 PHP 中采用了第二种思路,并且做了比较多的优化。在 PHP 中,所有的变量都会对应同一种类型 zval,其中 zval也就是 struct _zval_struct,具体定义如下:typedef union _zvalue_value long lval; /* long value
3、 */double dval; /* double value */struct char *val;int len; str;HashTable *ht; /* hash table value */zend_object_value obj; zvalue_value;struct _zval_struct /* Variable information */zvalue_value value; /* value */zend_uint refcount;zend_uchar type; /* active type */zend_uchar is_ref;从 zval 可以看出,PHP
4、 在细节方面的确做了不少优化的功夫。1.zend_uchar type。采用 uchar 节省内存。2.zvalue_value value; 采用 union 来替换 void *,这样同样能节省空间,并且比 void *更能表义清晰。3.在字符串类型中,默认保留了字符串的长度。这样很容易做到字符串的二进制安全,并且在计算字符串长度的时候不需要进行扫描。观察 PHP 弱变量的实现,也会有以下疑惑:1.为什么会没有 int 类型呢?其实在 PHP 中是有的,只是说默认 int 数据就保存在 long中。2.资源类型咋表现的呢?资源在 PHP 内部其实就是一数字。详细后续会介绍。3.refcou
5、nt 和 is_ref 是干嘛的呢?呵呵,这就是第二部分要介绍的了。Reference counting (z)-is_ref = 0;#define SEPARATE_ZVAL_IF_NOT_REF(ppzv) /非引用下的变量分离if (!PZVAL_IS_REF(*ppzv) SEPARATE_ZVAL(ppzv); #define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) /非引用下的变量分离,并且设置引用if (!PZVAL_IS_REF(*ppzv) SEPARATE_ZVAL(ppzv); (*(ppzv)-is_ref = 1; #define SE
6、PARATE_ARG_IF_REF(varptr) /引用下的变量分离if (PZVAL_IS_REF(varptr) zval *original_var = varptr; ALLOC_ZVAL(varptr); varptr-value = original_var-value; varptr-type = original_var-type; varptr-is_ref = 0; varptr-refcount = 1; zval_copy_ctor(varptr); else varptr-refcount+; 这里面谈到两个重要的概念:1、非引用下的变量分离。非引用下的变量分离,是
7、指在一堆非引用变量中插入引用的情况下,在 PHP 内部进行的一种内存操作。以下面的列子来看:$a = 1;$b = $a;$c = 在前两句执行之后,内存结构如下图在第三句 $c = 语句中则会执行“非引用下的变量分离。 ”,具体步骤是:将 b 分离出来,同时把 a 对应的 zval 的 refcount-1。copy 出一个新的 zval,并把 zval 的 is_ref 设置成1.把 C 指向这个新的 zval,同时 refcount +最终效果如下图:2、引用下的变量分离。引用下的变量分离,是指在一堆引用变量中进行一个非引用赋值操作,这个时候会直接执行 copy 内存的操作。以下面的例子
8、来说$a = 1;$b = $c = $b;在执行完前两行后,PHP 中内存结构如下:在第三句,则会执行“引用下的变量分离”也就是真正的 copy,最终内存结构如下图据此,基本上对 PHP 变量内部的一些原理比较清楚了,但还有一些需要注意点的:1、PHP 变量的引用计数特性,对于数组同样也存在。但注意,对于 key 则不生效。(具体在后面章节会分析到。)2、PHP 变量中的对象比较特殊,在 PHP5之后,默认都是采用引用赋值的方式。具体实现可以参考 Zend_objects.*系列代码。3、对于分析 PHP 内部变量,推荐采用 xdebug_debug_zval,而不要采用内置的debug_zval_dump。因为 PHP 内置的 debug_zval_dump 函数一方面无法处理 is_ref,而且采用了引用的方式来处理,从而导致看到结果会有误解。使用技巧结论据此可以得出分析出不少结论:1、在 PHP 开发中不推荐采用引用。因为 PHP 内部对内存优化本身做了不少工作,引用不会带来太多优化。(但注意推荐非强制 )2、在 PHP 中 strlen 是 o(1)的。