一、引言记得网上流传甚广的段子“PHP是世界上最好的语言”,暂且不去讨论是否言过其实,但至少PHP确实有独特优势的,比如它的弱类型,即只需要$符号即可声明变量,使得PHP入手门槛极低,成为大家所青睐的Web服务端语言。那么它的变量是如何实现的呢?我们今天就来学习一下PHP的基本变量。
PHP的变量存储在zval结构体中,在执行阶段中编译为op_array时就能看到zval的身影。结构体定义在Zend/zend_types.h中,定义内容如下所示:
struct _zval_struct {
zend_value value;
union { struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
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;
};二、结构体剖析2.1、zend_value结构体的第一个变量是zend_value,顾名思义,它其实也是一个结构体,用于存放变量的值,比如整型、浮点型、引用计数、字符串、数组、对象、资源等。zend_value定义了众多类型的指针,但这些类型并不都是变量的类型,有些是给内核自己使用的,比如指针ast、zv、ptr。
typedef union _zend_value {
zend_long lval;
double dval;
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1; uint32_t w2;
} ww;
} zend_value;2.2、u1u1是一个联合体,它联合了结构体v和整型type_info。下面我们先来看一下结构体v的构成。
union { struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type,
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v; uint32_t type_info;
} u1;2.2.1、typetype是指变量的类型,刚在2.1中讲到了zend_value是用来存储变量的值,所以也应该有地方存储变量的类型,而这就是type的职责。以下是PHP定义的所有变量类型,有我们熟知的布尔、NULL、浮点、数组、字符串等类型。也有陌生的undef、indirect、ptr类型,变量类型在下一章中详解,这里不再赘述。
#define IS_UNDEF 0#define IS_NULL 1#define IS_FALSE 2#define IS_TRUE 3#define IS_LONG 4#define IS_DOUBLE 5#define IS_STRING 6#define IS_ARRAY 7#define IS_OBJECT 8#define IS_RESOURCE 9#define IS_REFERENCE 10#define IS_CONSTANT 11#define IS_CONSTANT_AST 12#define _IS_BOOL 13#define IS_CALLABLE 14#define IS_INDIRECT 15#define IS_PTR 172.2.2、type_flags
可以把它理解为子类型,上面提到了变量的类型,这个是针对不同类型的子类型或标记,type_flags一共有以下6种。
#define IS_TYPE_CONSTANT (1<<0) #define IS_TYPE_IMMUTABLE (1<<1) #define IS_TYPE_REFCOUNTED (1<<2) #define IS_TYPE_COLLECTABLE (1<<3) #define IS_TYPE_COPYABLE (1<<4) #define IS_TYPE_SYMBOLTABLE (1<<5)2.2.3、const_flags
常量类型的标记,对应的属性为:
#define IS_CONSTANT_UNQUALIFIED 0x010#define IS_LEXICAL_VAR 0x020#define IS_LEXICAL_REF 0x040#define IS_CONSTANT_CLASS 0x080 #define IS_CONSTANT_IN_NAMESPACE 0x1002.2.4、type_info
type_info与结构体v共用内存,修改type_info等同于修改结构体v的值,所以type_info是v中四个char的组合。
2.3、u2本来使用u1和zend_value就可以表示变量的,没有必要定义u2,但是我们来看一下,如果没有u2,在内存对齐的情况下zval内存大小为16个字节,当联合了u2后依然是占用16个字节。既然有或没有占用内存大小相同,不如用它来记录一些附属信息。下面我们来看下u2都存储了哪些内容。
2.3.1、next用来解决哈希冲突问题,记录冲突的下一个元素位置。
2.3.2、cache_slot运行时缓存,在执行函数时回去缓存中查找,若缓存中没有则到全局function表中查找。
2.3.3、lineno文件执行的行号,应用在AST节点上。Zend引擎在词法和语法解析时会把当前执行的文件行号记录下来,记录在zend_ast中的lineno中。
2.3.4、num_args函数调用时传入函数的参数个数。
2.3.5、fe_pos用于遍历数组时记录当前遍历的位置,比如每次执行foreach时fe_pos都会加一,当再次调用foreach进行遍历时,fe_post会进行重置。
2.3.6、fe_iter_idx这个与fe_pos类似,只不过它是针对对象的。对象的属性也是HashTable,传入的参数是对象时,会获取对象的属性,所以遍历对象就是在变量对象的属性。
三、参考文献《PHP7内核剖析》
《PHP7底层设计和源码实现》
深入理解PHP7内核之zval
原文出处:https://www.cnblogs.com/enochzzg/p/9626946.html



