PHP扩展开发
PHP的基本变量
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字段的含义
- type: 记录变量类型
- type_flag: 对应变量类型的特有的标记
IS_TYPE_CONSTANT /* 是常量类型 */
IS_TYPE_IMMUTABLE /* 不可变的类型 */
IS_TYPE_REFCOUNTED /* 需要引用计数的类型 */
IS_TYPE_COLLECTABLE /* 可能包含循环引用的类型(IS_ARRAY, IS_OBJECT) */
IS_TYPE_COPYABLE /* 可被复制的类型 */
- const_flag: 常量类型的标记
#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字段
/* 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();
}