PHP内核原理(一)Zvals基本结构
来源:互联网 发布:网络歌曲新打工谣 编辑:程序博客网 时间:2024/06/05 13:21
转载请注明出处http://blog.csdn.net/fanhengguang_php
Zvals 基本结构
php内核中使用zval表示一个php变量。
一个zval(zend value 的简写)结构可以表示一个任意的php变量,这是整个php内核中最重要的数据结构,本章将会介绍zval的基本概念以及如何使用。
Types and values
每个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
)类型的值存储在
struct { char *val; int len; } str;
可见其包含一个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**
zval zv;zval *zv_ptr;zval **zv_ptr_ptr;zval ***zv_ptr_ptr_ptr;Z_TYPE(zv); // = zv.typeZ_TYPE_P(zv_ptr); // = zv_ptr->typeZ_TYPE_PP(zv_ptr_ptr); // = (*zv_ptr_ptr)->typeZ_TYPE_PP(*zv_ptr_ptr_ptr); // = (**zv_ptr_ptr_ptr)->type
基本上P后缀的数量和*
的数量是一致的, 这个规则适用于zval**
以内,对于zval***
是没有特定访问宏的, 因为其极少用到。
如Z_LVAL
,还有很多其他的访问宏用于访问其他类型的value,为了演示他们的用法,我们看下面这个zval打印函数。
PHP_FUNCTION(dump){ zval *zv_ptr; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zv_ptr) == FAILURE) { return; } switch (Z_TYPE_P(zv_ptr)) { case IS_NULL: php_printf("NULL: null\n"); break; case IS_BOOL: if (Z_BVAL_P(zv_ptr)) { php_printf("BOOL: true\n"); } else { php_printf("BOOL: false\n"); } break; case IS_LONG: php_printf("LONG: %ld\n", Z_LVAL_P(zv_ptr)); break; case IS_DOUBLE: php_printf("DOUBLE: %g\n", Z_DVAL_P(zv_ptr)); break; case IS_STRING: php_printf("STRING: value=\""); PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr)); php_printf("\", length=%d\n", Z_STRLEN_P(zv_ptr)); break; case IS_RESOURCE: php_printf("RESOURCE: id=%ld\n", Z_RESVAL_P(zv_ptr)); break; case IS_ARRAY: php_printf("ARRAY: hashtable=%p\n", Z_ARRVAL_P(zv_ptr)); break; case IS_OBJECT: php_printf("OBJECT: ???\n"); break; }}const zend_function_entry funcs[] = { PHP_FE(dump, NULL) PHP_FE_END};
运行结果如下:
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 value的设置
上面介绍的一些存取宏,仅仅提供了某些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
定义如下:
#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)
由于其包含一个类型转换操作,所以你不能这样操作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()
进行拷贝, 这样我们的例子可以重写如下:
PHP_FUNCTION(hello_world) { ZVAL_STRINGL(return_value, "hello world!", strlen("hello world!"), 1);}
更进一步,我们无需手动计算字符串长度strlen, 可以使用ZVAL_STRING
后面没有L
PHP_FUNCTION(hello_world) { ZVAL_STRING(return_value, "hello world!", 1);}
如果你知道字符串长度(因为字符串长度可能会传递给你), 你应该使用ZVAL_STRINGL
宏来保证二进制安全。如果你不知道字符串长度(或者知道字符串中不包含NUL字节)你可以使用ZVAL_STRING
代替。
除了ZVAL_STRING(L)
之外,还有一些用于设置zval 值的宏,如下:
ZVAL_NULL(return_value);ZVAL_BOOL(return_value, 0);ZVAL_BOOL(return_value, 1);/* or better */ZVAL_FALSE(return_value);ZVAL_TRUE(return_value);ZVAL_LONG(return_value, 42);ZVAL_DOUBLE(return_value, 4.2);ZVAL_RESOURCE(return_value, resource_id);ZVAL_EMPTY_STRING(return_value);/* = ZVAL_STRING(return_value, "", 1); */ZVAL_STRING(return_value, "string", 1);/* = ZVAL_STRING(return_value, estrdup("string"), 0); */ZVAL_STRINGL(return_value, "nul\0string", 10, 1);/* = ZVAL_STRINGL(return_value, estrndup("nul\0string", 10), 10, 0); */
注意:这些宏只负责设置zval的值, 不会销毁之前的已经存在的值,对于return_value
是没问题的,因为它被初始化为IS_NULL
类型(没有任何value需要被释放). 但是其他情况下你需要先释放掉老的value值,具体方法下节介绍。
- PHP内核原理(一)Zvals基本结构
- 深入理解php内核 编写扩展 II:参数、数组和ZVALs
- Windows内核原理与实现--Windows基本结构概述
- PHP内核中的结构
- php 内核原理
- Win内核原理与实现学习笔记2-现代操作系统的基本结构
- PHP基本工作原理
- jvm 基本结构 一
- PHP内核中的哈希表结构
- PHP内核中的基本数据类型
- storm 基本结构及原理
- 单片机原理(1):基本结构
- windows内核原理与实现学习笔记 (一) windows 系统结构
- PHP的基本语言结构
- PHP基本的语法结构
- php扩展的基本结构
- PHP内核之PHP.INI配置原理
- PHP Internals Book PHP内核原理
- RTEMS移植USB无线网卡的设想
- 皮皮学Web第一弹——了解Web app的目录结构
- opencv学习(三十四)之重映射remap
- 制作VOC2007数据集用于Faster-RCNN训练
- POJ 3318 Matrix Multiplication 看看书
- PHP内核原理(一)Zvals基本结构
- 文件操作
- 查验身份证
- 算法训练 出现次数最多的数
- vue2 + vuex 高度还原 饿了么 App,用真实数据登陆官网,并实现购物车、下单功能
- 使用dom4j解析器解析xml文件
- 树链剖分——模板整理
- 论文翻译: 基于R-FCN的物体检测
- 1081. Rational Sum