zval
PHP在内核中是通过zval这个结构体来存储变量的,PHP的变量是弱类型的,首先来看看PHP5的zval设计
PHP5的zval
1 2 3 4 5 6 7 8 9 10
| /* 考虑到结构体对齐 大小为24字节 */ struct _zval_struct{ /*变量信息*/ zvalue_value value; // 16字节 zend_uint refcount__gc; // 4字节 zend_uchar type; /* 类型 1字节*/ zend_uchar is_ref__gc; // 1字节 };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| /* 以下说的大小是在 x86-64下 在内存对齐的情况下_zvalue_value的大小为16字节
*/ typedef union _zvalue_value { long lval; // 8字节 double dval; // 8字节 struct{ char *val; int len; } str; // 12字节 HashTable *ht; /*HashTable 数组 8字节*/ zend_object_value obj; // 12字节 zend_ast *ast; // 8字节 }zvalue_value;
|
PHP5的zval核心由一个zvalue_value类型的联合体和zend_uchar类型的type组成,
refcount__gc
是用来进行垃圾回收的,is_ref__gc
表示是否是引用类型
PHP5.3 为了解决循环引用实际申请的结构体为_zval_gc_info
1 2 3 4 5 6 7 8 9 10
| /* 该大小为 32字节 */ typedef struct _zval_gc_info { zval z; union { gc_root_buffer *buffered; struct _zval_gc_info *next; } u; // 4字节 }zval_gc_info;
|
zval_gc_info 在内存池中分配,内存池会为每个zval_gc_info额外申请一个大小为16字节的zend_mm_block结构体,用来存放内存相关信息
1 2 3 4 5 6 7 8
| typedef struct _zend_mm_block_info { size_t _size; size_t _prev; } zend_mm_block_info;
typedef struct _zend_mm_block{ zend_mm_block_info info; } zend_mm_block;
|
最终一个变量在PHP5中占48字节
PHP7的zval
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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; /* zval类型 */ void *ptr; /* 指针类型 */ zend_class_entry *ce; /* class类型 */ zend_function *func; /* function类型 */ struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* zval类型 */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ uint32_t extra; /* not further specified */ } u2; };
|
可以看到PHP7中zval定义的好像比较复杂但都是联合体,所以实际占用大小只有16字节
接下类看看各个字段的含义。
u1字段的含义
- type: 记录变量类型
- type_flag: 对应变量类型的特有的标记
1 2 3 4 5
| IS_TYPE_CONSTANT /* 是常量类型 */ IS_TYPE_IMMUTABLE /* 不可变的类型 */ IS_TYPE_REFCOUNTED /* 需要引用计数的类型 */ IS_TYPE_COLLECTABLE /* 可能包含循环引用的类型(IS_ARRAY, IS_OBJECT) */ IS_TYPE_COPYABLE /* 可被复制的类型 */
|
- const_flag: 常量类型的标记
1 2 3 4
| #define IS_CONSTANT_UNQUALIFIED 0x010 #define IS_CONSTANT_VISITED_MARK 0x020 #define IS_CONSTANT_CLASS 0x080 #define IS_CONSTANT_IN_NAMESPACE 0x100
|
- reserved: 保留字段
u2字段的含义,主要是辅助字段
- next: 用于哈希冲突中记录下一个元素的位置
- cache_slot: 运行时缓存
- lineno: 文件执行的行号,应用在AST节点上
- num_args: 函数调用时传入参数的个数
- fe_pos: 遍历数组时的当前位置
- fe_iter_idx: 与fe_pos类似,针对对象使用
- access_flags: 对象类的访问标志, public protected private
- property_guard: 防止类中魔术方法的循环调用
PHP7 变量类型
PHP7 通过定义20种宏,来u1.v.type字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| /* regular data types */ #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
/* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12
/* fake types */ #define _IS_BOOL 13 #define IS_CALLABLE 14 #define IS_ITERABLE 19 #define IS_VOID 18
/* internal types */ #define IS_INDIRECT 15 /* 间接类型 */ #define IS_PTR 17 /* 指针类型 */ #define _IS_ERROR 20 /* 错误类型 *
|
整形和浮点型
整形和浮点型在zval是直接存储的,实现的比较简单
字符串类型
字符串类型的结构如下共占32字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| struct _zend_string{ zend_refcounted_h gc; // 用于引用计数 8字节 zend_ulong h; // 哈希值 8字节 size_t len; // 字符串长度 8字节 char val[1]; // 柔性数组,1字节 字符串的值存储位置 };
typedef struct _zend_refcounted_h { uint32_t refcounted; union { struct{ ZEND_ENDIAN_LOHI_3 ( zend_uchar type, zend_uchar flags, uint16_t gc_info ) } v; uint32_t type_info; } u; }zend_refcounted_h; ``` #### 字段说明 gc gc字段主要存放引用计数等信息。flags存放类别信息,type存放变量类别
h h字段的作用是缓存字符串的哈希值
val val字段存储字符串值
len 字符串长度
PHP中的字符串是二进制安全的,在遇到\0的时候并不认为结束了,读取的时候以len的值为准
#### 几个常用字符串的操作 echo 输出一个或多个字符串 在Zend/zend_vm_execute.h下
|
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ECHO_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
USE_OPLINE
zval *z;
SAVE_OPLINE();
z = EX_CONSTANT(opline->op1); //获取变量
if (Z_TYPE_P(z) == IS_STRING) { // 判断是否是字符串 是的或就直接输出
zend_string *str = Z_STR_P(z);
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str)); //将字符串写入到标准输出
}
} else {
zend_string *str = _zval_get_string_func(z); //强制转换
if (ZSTR_LEN(str) != 0) {
zend_write(ZSTR_VAL(str), ZSTR_LEN(str)); //写入到标准输出
} else if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(z) == IS_UNDEF)) {
GET_OP1_UNDEF_CV(z, BP_VAR_R);
}
zend_string_release(str);
}
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}