转载请注明出处http://blog.csdn.net/fanhengguang_php
php内核中使用zval表示一个php变量。
一个zval(zend value 的简写)结构可以表示一个任意的php变量,这是整个php内核中最重要的数据结构,本章将会介绍zval的基本概念以及如何使用。
每个zval中存储了一个变量的值以及变量的类型。 这点非常必要,因为php是一个动态类型的语言,变量的类型是在运行阶段确定的,而不是编译阶段。另外变量的类型在zval的声明周期内是可以改变的,所以一个zval可能开始的时候存储的是int整型,过一会又变成了字符串string类型。
zval的类型用一个整形来标记(unsigned char)。 它可以有8种值,对应php中的8中变量类型, 变量的值可以通过一个常量的范式IS_TYPE来访问, 如IS_NULL代表null类型, IS_STRING代表string类型。
zval的实际的值存储在一个unin结构中,如下:
typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj;} zvalue_value;对于不熟悉unin结构的同学来说:unin定义了多个成员变量, 但是任意时刻只有一个成员变量被使用, 不熟悉的同学可以自行GOOGle
通过zvals的type 标记就可以知道当前union中那个成员正在被使用, 在介绍相关api之前, 我们先简单看下php支持哪些不同类型的变量,以及是如何存储的。
IS_NULL
是最简单的类型, 并不需要存储任何值,因为null类型只有一个null值,
php通过long lval 和double dval 成员变量来存储IS_LONG 和IS_DOUBLE类型, 前者表示整型,后者表示浮点型。
对于long型需要注意的是,首先它是一个有符号类型,也就是说既可以存储正整数也可以存储负数,但是其对于按位运算不太合适; 其次long型在不同平台上会有不同长度,对于32位系统为4bytes,对于64位系统长度可能是4或者8bytes。
基于以上原因,你不能依赖特定长度的long型, long型的最大最小值可以通过常量LONG_MAX
和 LONG_MIN
来获的。 长度可以通过SIZEOF_LONG得到。
double类型用来存储浮点型,通常为8types,但是你应该意识到这个类型的精度也是有局限的,有时存储的并不是你想要的结果。
布尔类型通过IS_BOOL
常量标记,其值存储在long lval
中, 0表示false, 1表示true。 由于布尔类型只有2个值,理论上可以用一个更小的类型如unsigned char 来表示, 但是由于zvalue_value
是一个union, 其占用内存的大小是由最大的成员决定的, 所有这里复用的lval。
字符串Strings(IS_STRING
)类型的值存储在
可见其包含一个char* 字符串指针,和int型字符串长度。 为保证二进制安全php字符串需要存储字符串的明确长度, 因为字符串中可能包含NUL(‘/0’)。 但是PHP底层字符串仍是用NUL(‘/0’)结尾的c字符串,这样的好处是很多现成的库并不支持显示传递字符串长度参数,当然这样的话就不是二进制安全的了,如果其中包含NUL字符,将会被截断。例如很多系统相关函数都是这样处理的,包括libc中的大部分函数。
字符串长度以bytes计算,是不包括结尾的NUL字符的,"foo"
长度为3,但是其占用了4个bytes, 如果你使用sizeof计算字符串长度,需要记得-1:strlen("foo") == sizeof("foo") -1
而且string长度是用int而不是long型存储的, 这是一个不幸的历史问题,这导致字符串的长度最大为2147483647, 超过限制会导致溢出(长度会变成负值).
余下的几个类型,将会简单介绍下, 后面会有独立的章节详细介绍:
Array 通过IS_ARRAY
标记, 其值存储在HashTable *ht
成员变量中。 Objects (IS_OBJECT
) 值存储在 zend_object_value
中, 他包含了一个用来查找这个对象的实际的数据的int object handle 对象句柄和一系列的用于决定这个对象行为的句柄。php 类和对象系统将会在后面介绍。
Resource(IS_RESOURCE
)和 objects类似,页存储了一个唯一ID用来查找实际的value。 这个ID存储在long lval成员变量中,后面有具体章节介绍resource。
总结一下:下表列出了php中的类型以及相应的value的实际存储位置:
Type tag Storage locationIS_NULL noneIS_BOOL long lvalIS_LONG long lvalIS_DOUBLE double dvalIS_STRING struct { char *val; int len; } strIS_ARRAY HashTable *htIS_OBJECT zend_object_value objIS_RESOURCE long lval我们看下zval 结构体的结构
typedef struct _zval_struct { zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc;} zval;前面已经提到过,zval的成员变量中存储了变量的值value 和变量的类型,value存储在我们上面讨论过的zvalue_value value;
union中,变量类型存储在zend_uchar
中。 另外有两个成员变量以__gc
结尾, 这两个变量用来进行垃圾回收处理的,这里暂且布标,后面再讨论。
在了解了zval结构之后, 你可以编码使用它了:
zval *zv_ptr = /* ... get zval from somewhere */;if (zv_ptr->type == IS_LONG) { php_PRintf("Zval is a long with value %ld/n", zv_ptr->value.lval);} else /* ... handle other types */虽然上面的代码是可行的,但是这并不是好的习惯,它直接访问了zval的成员而不是通过一系列访问宏:
zval *zv_ptr = /* ... */;if (Z_TYPE_P(zv_ptr) == IS_LONG) { php_printf("Zval is a long with value %ld/n", Z_LVAL_P(zv_ptr));} else /* ... */上面的代码中使用了Z_TYPE_P()
宏来获取类型标记, 用Z_LVAL_P()
来获取zval中的long型value。 所有这些存取宏都类似于这样_P后缀, _PP后缀, 或者没有后缀。使用哪一种形式取决于你当前是在处理zval, zval* 还是zval**
基本上P后缀的数量和*
的数量是一致的, 这个规则适用于zval**
以内,对于zval***
是没有特定访问宏的, 因为其极少用到。
如Z_LVAL
,还有很多其他的访问宏用于访问其他类型的value,为了演示他们的用法,我们看下面这个zval打印函数。
运行结果如下:
dump(null); // NULL: nulldump(true); // BOOL: truedump(false); // BOOL: falsedump(42); // LONG: 42dump(4.2); // DOUBLE: 4.2dump("foo"); // STRING: value="foo", length=3dump(fopen(__FILE__, "r")); // RESOURCE: id=???dump(array(1, 2, 3)); // ARRAY: hashtable=0x???dump(new stdClass); // OBJECT: ???这些zval 访问宏是非常直接了当的: Z_BVAL bools
对应bool类型 Z_LVAL
对应long型, Z_DVAL
对应double类型。 Z_STRVAL
返回实际的字符串测char*
指针,Z_STRLEN
返回字符串长度。资源id通过Z_RESVAL
获取。数组类型zval的HashTable*
通过Z_ARRVAL
宏获取。object类型value的获取暂且不表,因为涉及到有些背景知识。
当你想要访问zval的value的时候, 你应当使用这些宏定义,而不是直接访问结构体、联合体的成员。通过这些宏定义访问zval的内容可以避免歧义和错误,而且可以保证PHP内核升级的兼容性。
上面介绍的一些存取宏,仅仅提供了某些zval结构成员变量的存取方法。可以通过这些访问对成员变量进行读写。对于一些常见的操作来说,通过上面的宏操作是比较复杂的,考虑以下函数,几年返回一个字符串hello world。
PHP_FUNCTION(hello_world) { Z_TYPE_P(return_value) = IS_STRING; Z_STRVAL_P(return_value) = estrdup("hello world!"); Z_STRLEN_P(return_value) = strlen("hello world!");};/* ... */ PHP_FE(hello_world, NULL)/* ... */执行php -r "echo hello_world();"
, 控制台输出hello world。
上线的例子我们设置了return_value
变量, 这是一个由PHP_FUNCTION
提供的zval*
指针。 后面我们会详细介绍它, 在这里你只需要知道这个值将会是这个函数的返回值,默认情况下它被初始化为IS_NULL
.
通过之前介绍的访问宏来操作zval非常简单,但是需要注意的是:你需要单独去设置zval的类型。仅仅设置内容(通过Z_STRVAL
and Z_STRLEN
here)是不行的。 你总是要注意自己手动设置类型标记。
另外你需要注意大多数情况下zval以及其包含的值的的生命周期会比你操作这个zval的上下文还要长, 有时也有例外,比如你操作一个临时的zvals。
对于上面的例子意味着return_value
在函数结束后会依然存在(这是显而易见的, 否则不会有人去使用return_value
了). 所以这里不能使用临时的变量,例如Z_STRVAL_P(return_value) = "hello world!"
是非法的, 以为字符串hello Word
将会随着函数退出而消失(对于c语言每个栈上分配的变量都是这样).
因此我们需要使用estrdup()
拷贝字符串,这将会在堆上创建一个独立的字符串拷贝, 由于这个字符串是zval的成员,当zval销毁的时候,需要确保这个字符串也被释放掉, 这个特性也适用于其他复杂的zval。 例如当你设置一个zval的HashTable*
后,zval将会接管这个变量,当zval被释放的时候,它也会被随之释放掉。对于那些简单类型例如整型或者double型来说不需要考虑这个问题。
最后,并不是所有的访问宏都会返回一个成员,Z_BVAL
定义如下:
由于其包含一个类型转换操作,所以你不能这样操作Z_BVAL_P(return_value) = 1
除去一些对象操作的宏,这是唯一的例外,其他的访问宏,可以用来设置value。
实际上你无需考虑字符串的结尾符, 因为设置zval的value是一个常用操作,所以php提供了另外的一些宏来提供这个功能, 通过这些宏你可以同时设定类型标记和zval的值,通过宏来重写上面的例子:
PHP_FUNCTION(hello_world) { ZVAL_STRINGL(return_value, estrdup("hello world!"), strlen("hello world!"), 0);}由于设置zval值时通常需要对字符串进行拷贝,你可以直接使用ZVAL_STRINGL
的最后一个参数完成这个操作,如果传递0则直接使用这个字符串, 如果传递1,字符串会通过estrndup()
进行拷贝, 这样我们的例子可以重写如下:
更进一步,我们无需手动计算字符串长度strlen, 可以使用ZVAL_STRING
后面没有L
如果你知道字符串长度(因为字符串长度可能会传递给你), 你应该使用ZVAL_STRINGL
宏来保证二进制安全。如果你不知道字符串长度(或者知道字符串中不包含NUL字节)你可以使用ZVAL_STRING
代替。
除了ZVAL_STRING(L)
之外,还有一些用于设置zval 值的宏,如下:
注意:这些宏只负责设置zval的值, 不会销毁之前的已经存在的值,对于return_value
是没问题的,因为它被初始化为IS_NULL
类型(没有任何value需要被释放). 但是其他情况下你需要先释放掉老的value值,具体方法下节介绍。
新闻热点
疑难解答
图片精选