注:这篇文章的内容出自ircmaxell的博客,这里只是翻译整理一下!
这节我们将继续在上节的基础上,探讨PHP的内核实现,上节主要讲了如何在PHP源码中找到一个PHP函数的实现, 并以strpos函数为例,简要分析了它的实现过程。这节我们主要分析一下PHP中的变量,即源码中随处可见的zval类型。
走进zval
PHP是一种弱类型语言,不需要变量的类型声明,解释器会根据上下文环境,来决定当前变量是什么类型。PHP有Intger、String、Boolean、Float四种基本类型;Array、Object两种复合类型;还有Resource、NULL两种特殊类型。那么这些“PHP类型”是如何映射到“C类型”的呢。上节我们简单提到,PHP的整型对应着C的long类型。
这些都跟zval结构体有关,在lxr.php.net中,搜索zval,会有很多结果,随便选一个进入,点击zval便可以跳转到zval的定义(在zend目录下的zend.h文件中)
1 typedef struct _zval_struct zval;
可见,zval是_zval_struct的别名,点击_zval_struct
1 struct _zval_struct { 2 /* Variable information */ 3 zvalue_value value; /* value */ 4 zend_uint refcount__gc; 5 zend_uchar type; /* active type */ 6 zend_uchar is_ref__gc; 7 };
这个结构体看起来很简单,只有四个成员变量,但PHP中所有的变量都出自这里,这个结构体就是我们接下来的重点,我将会挨个分析这四个成员变量。
Value
这个变量是zvalue_value类型,用于存储PHP中变量的值,如:$var = 100;值100就存储在这里,点击跳到定义处。
1 typedef union _zvalue_value { 2 long lval; /* long value */ 3 double dval; /* double value */ 4 struct { 5 char *val; 6 int len; 7 } str; 8 HashTable *ht; /* hash table value */ 9 zend_object_value obj; 10 } zvalue_value;
注意到这是一个union类型,大家知道C语言中的union类型是一种很有趣的类型,它里面的成员变量都使用同一块内存,系统也只为这些变量分配一块内存,这块内存的大小取决于成员变量中占内存最大的变量,这就保证了这块内存可以容下任何一个成员变量。所以,union类型可以让一个变量根据访问的不同,被解释为不同的类型,比如:zvalue_value.lval = 123.321,内存中就会存一个long类型的变量。
现在,我们仔细看看zvalue_value里的成员变量,一共有5个。
- long lval 表示PHP中的整型
- double dval 表示PHP中的浮点型
- struct{...} 表示PHP中的字符串
- HashTable *ht 表示PHP中的数组
- zend_object_value obj 表示PHP中的对象
似乎还有Boolean、Resource、NULL三种没有提到,OK,已有的类型已经足够存储了,Boolean、Resource被存储为long,NULL不需要存储。
TYPE
OK,假设我们在PHP中定义了一个变量:$var = 'hello';对应到C代码,在给上面的value赋值的时候,我们怎么知道当前变量的类型的呢,这里的type成员变量就是实现这个功能的,如:tyle标识为String类型,就会通过value.str.char = 'hello'。
zend目录下的zend.h文件定义了各种类型。
1 #define IS_NULL 0 2 #define IS_LONG 1 3 #define IS_DOUBLE 2 4 #define IS_BOOL 3 5 #define IS_ARRAY 4 6 #define IS_OBJECT 5 7 #define IS_STRING 6 8 #define IS_RESOURCE 7
IS_REF
这个变量是否被引用,比如:$foo = &$mm,那么$foo对应的is_ref会被赋值为1,否则为0。
REFCOUNT
引用次数,如果为1,表示只有一个变量指向这个zval示例,如果为2,表示有两个变量指向这个zval示例。is_ref和refcount结合底层优化和垃圾回收有重要的作用,想更深入的理解,可以参考这里。
至此,zval的成员变量介绍完毕。
如何操作zval
zval应该是内核中操作最频繁的结构体了,对zval的增删改查操作,是通过定义了几个宏来实现的,这样做很方便,我们在上一节中也遇到过这些宏指令。
在zend目录下的zend_operators.h文件中,我们可以看到:
1 #define Z_LVAL(zval) (zval).value.lval 2 #define Z_BVAL(zval) ((zend_bool)(zval).value.lval) 3 #define Z_DVAL(zval) (zval).value.dval 4 #define Z_STRVAL(zval) (zval).value.str.val 5 #define Z_STRLEN(zval) (zval).value.str.len 6 #define Z_ARRVAL(zval) (zval).value.ht 7 #define Z_OBJVAL(zval) (zval).value.obj
...
这些宏指令很精简 ,这里就不再介绍了
类型转化
PHP会自动为我们做类型转化的,因此你可以将一个字符串当作整型使用,在内核中是通过convert_to_type函数来完成的,它将会zval的type改为整型。
Zend_Parse_Parameters
这个函数我们上节中提到过,如果你想了解内核中类型的转换过程,这个函数的源码肯定可以让你大饱眼福,详情可以在这里看到。
下一篇博文
下一节,我们将会探讨PHP内核中数组的实现。