JOE'S BLOG

好记性不如烂键盘

0%

PHP的基本变量

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字段的含义

  1. type: 记录变量类型
  2. 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 /* 可被复制的类型 */
  1. 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
  1. reserved: 保留字段

u2字段的含义,主要是辅助字段

  1. next: 用于哈希冲突中记录下一个元素的位置
  2. cache_slot: 运行时缓存
  3. lineno: 文件执行的行号,应用在AST节点上
  4. num_args: 函数调用时传入参数的个数
  5. fe_pos: 遍历数组时的当前位置
  6. fe_iter_idx: 与fe_pos类似,针对对象使用
  7. access_flags: 对象类的访问标志, public protected private
  8. 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();

}