教程地址:PHP扩展开发及内核应用
二、PHP变量在内核中的实现
在强类型的编程语言中,我们必须在使用变量前先声明(定义)变量的类型和名称。
而PHP属于弱类型的编程语言,PHP 会根据变量的值,自动把变量转换为正确的数据类型。
1. 变量的类型
PHP中,一共有8种数据类型:
- 包括4中标量数据类型:即boolean(布尔类型),integer(整型),float/double(浮点型),string(字符串型);
- 两种复合数据类型:即array(数组),object(对象);
- 两种特殊的数据类型:即resource(资源),null(无,空白);
这8种数据类型在内核中的分别对应于以下特定的常量(表格来源:http://www.cunmou.com/phpbook/2.1.md):
常量名称 | 注释 |
---|---|
IS_NULL | 第一次使用的变量如果没有初始化过,则会自动的被赋予这个常量,当然我们也可以在PHP语言中通过null这个常量来给予变量null类型的值。 这个类型的值只有一个 ,就是NULL,它和0与false是不同的。 |
IS_BOOL | 布尔类型的变量有两个值,true/false。在PHP语言中,while、if等语句会自动的把表达式的值转成这个类型的。 |
IS_LONG | PHP语言中的整型,在内核中是通过所在操作系统的signed long数据类型来表示的。 在最常见的32位操作系统中,它可以存储从-2147483648 到 +2147483647范围内的任一整数。 有一点需要注意的是,如果PHP语言中的整型变量超出最大值或者最小值,它并不会直接溢出, 而是会被内核转换成IS_DOUBLE类型的值然后再参与计算。 再者,因为使用了signed long来作为载体,所以这也就解释了为什么PHP语言中的整型数据都是带符号的了。 |
IS_DOUBLE | PHP中的浮点数据是通过C语言中的signed double型变量来存储的, 这最终取决与所在操作系统的浮点型实现。 我们做为程序猿,应该知道计算机是无法精准的表示浮点数的, 而是采用了科学计数法来保存某个精度的浮点数。 用科学计数法,计算机只用8位便可以保存2.225x10(-308)~~1.798x10308之间的浮点数。 用计算机来处理浮点数简直就是一场噩梦,十进制的0.5专成二进制是0.1, 0.8转换后是0.1100110011…。 但是当我们从二进制转换回来的时候,往往会发现并不能得到0.8。 我们用1除以3这个例子来解释这个现象:1/3=0.3333333333…,它是一个无限循环小数, 但是计算机可能只能精确存储到0.333333,当我们再乘以三时, 其实计算机计算的数是0.333333*3=0.999999,而不是我们平时数学中所期盼的1.0。 |
IS_STRING | PHP中最常用的数据类型——字符串,在内存中的存储和C差不多, 就是一块能够放下这个变量所有字符的内存,并且在这个变量的zval实现里会保存着指向这块内存的指针。 与C不同的是,PHP内核还同时在zval结构里保存着这个字符串的实际长度, 这个设计使PHP可以在字符串中嵌入‘\0’字符,也使PHP的字符串是二进制安全的, 可以安全的存储二进制数据!本着艰苦朴素的作风,内核只会为字符串申请它长度+1的内存, 最后一个字节存储的是‘\0’字符,所以在不需要二进制安全操作的时候, 我们可以像通常C语言的方式那样来使用它。 |
IS_ARRAY | 数组是一个非常特殊的数据类型,它唯一的功能就是聚集别的变量。 在C语言中,一个数组只能承载一种类型的数据,而PHP语言中的数组则灵活的多, 它可以承载任意类型的数据,这一切都是HashTable的功劳, 每个HashTable中的元素都有两部分组成:索引与值, 每个元素的值都是一个独立的zval(确切的说应该是指向某个zval的指针)。 |
IS_OBJECT | 和数组一样,对象也是用来存储复合数据的,但是与数组不同的是, 对象还需要保存以下信息:方法、访问权限、类常量以及其它的处理逻辑。 相对与zend engine V1,V2中的对象实现已经被彻底修改, 所以我们PHP扩展开发者如果需要自己的扩展支持面向对象的工作方式, 则应该对PHP5和PHP4分别对待! |
IS_RESOURCE | 有一些数据的内容可能无法直接呈现给PHP用户的, 比如与某台mysql服务器的链接,或者直接呈现出来也没有什么意义。 但用户还需要这类数据,因此PHP中提供了一种名为Resource(资源)的数据类型。 |
来源内核zend_operators.h中的一段代码:
#define convert_to_explicit_type(pzv, type)
do {
switch (type) {
case IS_NULL:
convert_to_null(pzv);
break;
case IS_LONG:
convert_to_long(pzv);
break;
case IS_DOUBLE:
convert_to_double(pzv);
break;
case IS_BOOL:
convert_to_boolean(pzv);
break;
case IS_ARRAY:
convert_to_array(pzv);
break;
case IS_OBJECT:
convert_to_object(pzv);
break;
case IS_STRING:
convert_to_string(pzv);
break;
default:
assert(0);
break;
}
} while (0);
2. 变量的值
PHP有8中变量类型,每种变量类型中的值,对应的不同的保存方法。
//操作整数的
#define Z_LVAL(zval) (zval).value.lval
#define Z_LVAL_P(zval_p) Z_LVAL(*zval_p)
#define Z_LVAL_PP(zval_pp) Z_LVAL(**zval_pp)
//操作IS_BOOL布尔型的
#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)
#define Z_BVAL_P(zval_p) Z_BVAL(*zval_p)
#define Z_BVAL_PP(zval_pp) Z_BVAL(**zval_pp)
//操作浮点数的
#define Z_DVAL(zval) (zval).value.dval
#define Z_DVAL_P(zval_p) Z_DVAL(*zval_p)
#define Z_DVAL_PP(zval_pp) Z_DVAL(**zval_pp)
//操作字符串的值和长度的
#define Z_STRVAL(zval) (zval).value.str.val
#define Z_STRVAL_P(zval_p) Z_STRVAL(*zval_p)
#define Z_STRVAL_PP(zval_pp) Z_STRVAL(**zval_pp)
#define Z_STRLEN(zval) (zval).value.str.len
#define Z_STRLEN_P(zval_p) Z_STRLEN(*zval_p)
#define Z_STRLEN_PP(zval_pp) Z_STRLEN(**zval_pp)
#define Z_ARRVAL(zval) (zval).value.ht
#define Z_ARRVAL_P(zval_p) Z_ARRVAL(*zval_p)
#define Z_ARRVAL_PP(zval_pp) Z_ARRVAL(**zval_pp)
//操作对象的
#define Z_OBJVAL(zval) (zval).value.obj
#define Z_OBJVAL_P(zval_p) Z_OBJVAL(*zval_p)
#define Z_OBJVAL_PP(zval_pp) Z_OBJVAL(**zval_pp)
#define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle
#define Z_OBJ_HANDLE_P(zval_p) Z_OBJ_HANDLE(*zval_p)
#define Z_OBJ_HANDLE_PP(zval_p) Z_OBJ_HANDLE(**zval_p)
#define Z_OBJ_HT(zval) Z_OBJVAL(zval).handlers
#define Z_OBJ_HT_P(zval_p) Z_OBJ_HT(*zval_p)
#define Z_OBJ_HT_PP(zval_p) Z_OBJ_HT(**zval_p)
#define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJCE_P(zval_p) Z_OBJCE(*zval_p)
#define Z_OBJCE_PP(zval_pp) Z_OBJCE(**zval_pp)
#define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJPROP_P(zval_p) Z_OBJPROP(*zval_p)
#define Z_OBJPROP_PP(zval_pp) Z_OBJPROP(**zval_pp)
#define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf
#define Z_OBJ_HANDLER_P(zval_p, h) Z_OBJ_HANDLER(*zval_p, h)
#define Z_OBJ_HANDLER_PP(zval_p, h) Z_OBJ_HANDLER(**zval_p, h)
#define Z_OBJDEBUG(zval,is_tmp) (Z_OBJ_HANDLER((zval),get_debug_info)? \
Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&is_tmp TSRMLS_CC): \
(is_tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL))
#define Z_OBJDEBUG_P(zval_p,is_tmp) Z_OBJDEBUG(*zval_p,is_tmp)
#define Z_OBJDEBUG_PP(zval_pp,is_tmp) Z_OBJDEBUG(**zval_pp,is_tmp)
//操作资源的
#define Z_RESVAL(zval) (zval).value.lval
#define Z_RESVAL_P(zval_p) Z_RESVAL(*zval_p)
#define Z_RESVAL_PP(zval_pp) Z_RESVAL(**zval_pp)
3.创建PHP变量
PHP变量在内核中,是使用zval结构来实现的。
新宏 | 其它宏的实现方法 |
---|---|
ZVAL_NULL(pvz); (注意这个Z和VAL之间没有下划线!) | Z_TYPE_P(pzv) = IS_NULL;(IS_NULL型不用赋值,因为这个类型只有一个值就是null,_) |
ZVAL_BOOL(pzv, b); (将pzv所指的zval设置为IS_BOOL类型,值是b) | Z_TYPE_P(pzv) = IS_BOOL; Z_BVAL_P(pzv) = b ? 1 : 0; |
ZVAL_TRUE(pzv); (将pzv所指的zval设置为IS_BOOL类型,值是true) | ZVAL_BOOL(pzv, 1); |
ZVAL_FALSE(pzv); (将pzv所指的zval设置为IS_BOOL类型,值是false) | ZVAL_BOOL(pzv, 0); |
ZVAL_LONG(pzv, l); (将pzv所指的zval设置为IS_LONG类型,值是l) | Z_TYPE_P(pzv) = IS_LONG; Z_LVAL_P(pzv) = l; |
ZVAL_DOUBLE(pzv, d); (将pzv所指的zval设置为IS_DOUBLE类型,值是d) | Z_TYPE_P(pzv) = IS_DOUBLE; Z_DVAL_P(pzv) = d; |
ZVAL_STRINGL(pzv,str,len,dup);(下面单独解释) | Z_TYPE_P(pzv) = IS_STRING; Z_STRLEN_P(pzv) = len; if (dup) { Z_STRVAL_P(pzv) =estrndup(str, len + 1); } else { Z_STRVAL_P(pzv) = str; } |
ZVAL_STRING(pzv, str, dup); | ZVAL_STRINGL(pzv, str,strlen(str), dup); |
ZVAL_RESOURCE(pzv, res); | Z_TYPE_P(pzv) = IS_RESOURCE; Z_RESVAL_P(pzv) = res; |
ZVAL_STRINGL(pzv,str,len,dup)中的dup参数
先阐述一下ZVAL_STRINGL(pzv,str,len,dup); str和len两个参数很好理解,因为我们知道内核中保存了字符串的地址和它的长度, 后面的dup的意思其实很简单,它指明了该字符串是否需要被复制。 值为 1 将先申请一块新内存并赋值该字符串,然后把新内存的地址复制给pzv, 为 0 时则是直接把str的地址赋值给zval。
《抚琴居》上的一篇文章说这项特性将会在你仅仅需要创建一个变量并将其指向一个已经由 Zend 内部数据内存时变得很有用。
ZVAL_STRINGL与ZVAL_STRING的区别
如果你想在某一位置截取该字符串或已经知道了这个字符串的长度, 那么可以使用宏 ZVAL_STRINGL(zval, string, length, duplicate) ,它显式的指定字符串长度, 而不是使用strlen()。这个宏该字符串长度作为参数。但它是二进制安全的,而且速度也比ZVAL_STRING快,因为少了个strlen。
ZVAL_RESOURCE约等于ZVAL_LONG
上一节中我们说过PHP中的资源类型的值其实就是一个整数,所以ZVAL_RESOURCE和ZVAL_LONG的工作差不多, 只不过它会把zval的类型设置为 IS_RESOURCE。