zval

PHP在内核中是通过zval这个结构体来存储变量的,PHP的变量是弱类型的,首先来看看PHP5的zval设计

PHP5的zval

/*
 考虑到结构体对齐 大小为24字节
*/
struct _zval_struct{
	/*变量信息*/
	zvalue_value value;     // 16字节
	zend_uint refcount__gc;  // 4字节
	zend_uchar type;         /* 类型   1字节*/
	zend_uchar is_ref__gc;   // 1字节
};
/*
 以下说的大小是在 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

/*
该大小为 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结构体,用来存放内存相关信息

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


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: 对应变量类型的特有的标记
IS_TYPE_CONSTANT        /* 是常量类型 */
IS_TYPE_IMMUTABLE       /* 不可变的类型 */
IS_TYPE_REFCOUNTED      /* 需要引用计数的类型 */
IS_TYPE_COLLECTABLE     /* 可能包含循环引用的类型(IS_ARRAY, IS_OBJECT) */
IS_TYPE_COPYABLE        /* 可被复制的类型 */
  1. const_flag: 常量类型的标记
#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字段

/* 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字节

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();
}