数据类型是一个语言最为基础的部分,它是高级语言抽象出来的一个概念,对于低级的语言来说是没有这个概念的,比如机器语言。对内存,CPU而言并没有什么类型之分,内存中的数据是没有差别的,高级语言为内存中这些无差异的数据指定了特定的计算方式,比如读取一个32位整型,就是告诉CPU按照整型的规则连续读取4个字节的数据。
数据类型的产生使得程序的编写更加规范,简洁,灵活,它是现代高级语言必不可少的一部分。与强类型语言不同,PHP中变量的数据类型并不是固定不变的,它可以根据不同的使用场景进行转化。
变量
变量是最常见的数据类型应用形式,它由三个主要部分组成:变量名,变量值,变量类型,PHP中变量名与变量值可以简单的对应为:zval,zend_value,这两个概念一定要区分开。
PHP中通过$符号定义一个变量,在定义的同时可以进行初始化,在变量使用前不需要提前声明。事实上普通变量定义的方式包含了两步:变量定义,变量初始化,只定义而不初始化变量也是可以的,比如:
$a;
$b=1;
这段代码在执行时会分配两个zval,也就是这里定义了两个变量,只不过$a没有值而已,相当于unset()了。
变量类型
PHP中的变量类型,也就是数据类型,宏观角度可分为以下8种。
- 标量类型:字符串,整型,浮点型,布尔型。
- 复合类型:数组,对象。
- 特殊类型:资源,NULL。
具体到内部实现上会细分出更多的类型,比如布尔型在内部实际分为IS_TRUE,IS_FALSE
两种,也有一些基于基础数据类型产生的特殊类型,比如引用。全部类型如下:
#define IS_UNDEF
#define IS_NULL
#define IS_FALSE
#define IS_TRUE
#define IS_LONG
#define IS_DOUBLE
#define IS_STRING
#define IS_ARRAY
#define IS_OBJECT
#define IS_RESOURCE
#define IS_REFERENCE
#define IS_CONSTANT
#define IS_CONSTANT_AST
#define _IS_BOOL
#define IS_CALLABLE
#define IS_INDIRECT
#define IS_PTR内部实现
PHP中通过zval这个结构体来表示一个变量,而不同类型的变量值则通过zval嵌入的一个联合体表示,即zend_value,通过zval,zend_value及不同类型的结构实现了PHP基础的数据类型。
typedef struct _zval_struct zval;
struct _zval_struct {
//变量值
zend_value value;
union {
struct {
ZEND_ENDIAN_LOHI_4(
//变量类型
zend_uchar type,
//类型掩码,各类型会有几种不同的几种属性,内存管理会用到
zend_uchar type_flags,
zend_uchar const_flags,
//预留字段,zend执行过程中会用来记录call info
zend_uchar reserved)
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next;
uint32_t cache_slot;
uint32_t lineno;
uint32_t num_args;
uint32_t fe_pos;
uint32_t fe_iter_idx;
} u2; //一些辅助值
};zval除了嵌入了一个zend_value用来保存具体的变量值,还有两个特殊的union;
- u1:这个结构看起来比较复杂,实际它只是联合了一个结构体v和一个32位无符号整型type_info,ZEND_ENDIAN_LOHI4这个宏是用来解决字节序问题的,它会根据系统的字节序决定struct v中4个成员的顺序,忽略即可。v中定义了4个成员,其中type用于标示value类型,即上个小节列举的ISXXX类型,type_flags是类型掩码,用于变量的内存管理。type_info实际上是将v结构的4个成员组合到了一起,v中的成员各占一个字节,总共4个字节,每个字节对应v的一个成员,可以直接通过type_info位移获取v成员的值。
- u2:这个结构纯粹用于一些辅助功能,zval结构的value,u1占用的空间分别为8byte,4byte,但是加起来却不是12byte,系统会进行字节对齐,value,u1将占用16byte,多的4byte将浪费,所以zval定义了一个u2结构把这4byte利用了,最终zval结构的大小就是16byte。
zend_value的结构比较简单,它是一个联合体,各类型根据自己的类型选择使用不同的成员,其中整型,浮点型的值直接存储在zend_value中,其他类型则是指针,指向具体类型的结构。
typedef union _zend_value {
zend_long lval; //整型
double dval; //浮点型
zend_refcounted *counted; //获取不同类型结构的gc头部
zend_string *str; //string字符串
zend_array *arr; //array数组
zend_object *obj; //object对象
zend_resource *res; //resource资源类型
zend_reference *ref; //引用类型
zend_ast_ref *ast; //下面几个都是内核使用的value
zval *zv; //指向另一个zval
void *ptr; //指针,通用类型
zend_class_entry *ce; //类
zend_function *func; //函数
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;zend_value中定义了众多类型的指针,这些类型并不全是变量的类型,有些类型只给内核自己使用,比如ast,ptr,zv等。
字符串
PHP中并没有使用char来表示字符串,而是为了字符串单独定义了一个结构:zend_string。
在zend_value中通过str指向具体的结构,zend_string除了字符串内容,还存储了其他几个信息,具体结构如下:
typedef struct _zend_string zend_string;
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h;
size_t len;
char val[1];
};该结构有4个成员。
- gc:变量的引用计数信息,用于内存管理。
- h:字符串通过Times 33算法计算得到的 Hash Code。
- len:字符串长度。
- val:字符串内容。
zend_string的几个成员含义都比较直观,比较特殊的是用于存储字符串内容的成员,这里并没有使用char类型,而是使用了一个可变数组val。val[1]并不表示它只能存储一个字节,在字符串分配时实际上是类似这样操作的:malloc(sizeof(zend_string)+字符串长度),也就是会多分配一些内存,而多出的这块内存的起始位置就是val,这样就可以直接将字符串内容存储到val中,通过val进行读取。如果val是一个指针char*,则需要额外分配一次内存,变长结构不仅可以节省一次内存分配,而是更有助于内存管理,free时直接释放zend_string即可。