PHP是弱类型语言,也就是说一个PHP变量可以保存任何的数据类型。但是PHP是使用C语言编写的,而C语言是强类型的语言,每个变量都有固定类型,不能随意改变变量的类型(可以通过强类型转换改变,不过有可能出现问题),在Zend引擎中是怎么做到一个变量保存任何的数据类型呢?
打开Zend/zend.h文件,会发现以下一些结构体:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
zval结构体就是通常用到的PHP变量(如$variable)在内核中的表示方式。在zval结构体中,可以看到4个成员变量,分别是
zvalue_value value
: 变量的值,PHP变量的值就保存在这里。zend_uint refcount__gc
: 变量引用数,变量引用计算器。zend_uchar type;zend_uchar type
: 变量的类型。zend_uchar is_ref__gc
: 变量是否被引用。
zval结构体的value成员变量是一个zvalue_value联合体,PHP能够保持任何的结构类型就是因为这个联合体。从zvalue_value联合体的成员变量中可以看到,不同的类型会保存到不同的成员变量中,这样就实现了PHP变量可以存储任何数据类型。例如,当变量是整数类型时,会保存到value的lval成员变量中;当变量的类型是字符串时,又会保存到value的str成员变量中。如下表展示了不同类型保存到对应的成员变量中。
PHP语言层类型 | 保存在zvalue_value的成员变量 |
---|---|
long,bool,resoure | lval |
double | dval |
string | str(len保存字符串的长度,val保存字符串的值) |
array | ht |
object | obj |
现在已经解决了一个PHP变量可以保存任意类型的问题,但是另一个问题又出现了,就是Zend引擎是怎么知道这个变量保存的是什么类型呢?我们注意到,zval结构体中有个type成员变量,这个成员变量就是保存一个PHP变量的类型。
Zend引擎定义了几种变量类型,如下:
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
每一个宏定义对应PHP语言层的一种类型,例如当zval的type成员变量等于IS_STRING时(zval.type==IS_STRING),说明这个变量的类型时字符串类型。
宏定义 | 表示类型 |
---|---|
IS_NULL | NULL类型(null) |
IS_LONG | 整数类型(int) |
IS_DOUBLE | 浮点类型(float) |
IS_STRING | 字符串类型(string) |
IS_ARRAY | 数组类型(array) |
IS_OBJECT | 对象类型(object) |
IS_BOOL | 布尔类型(bool) |
IS_RESOURCE | 资源类型(resource) |
可以通过下面的代码打印一个zval的类型:
switch(zval.type) {
case IS_NULL:
php_printf("zval type is null\n");
break;
case IS_STRING:
php_printf("zval type is string\n");
break;
case IS_LONG:
php_printf("zval type is long\n");
break;
case IS_ARRAY:
php_printf("zval type is array\n");
break;
...
}