zval结构体
PHP5的zval的定义:结构体的大小是(在64位系统)24个字节
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};
PHP7的zval的定义: 全部都是联合体, 这个新的zval在64位环境下,现在只需要16个字节(2个指针size)
struct _zval_struct {
zend_value value; /* value 根据u1de type来决定使用哪个类型*/
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type, /* active type 代表数据的类型*/
zend_uchar type_flags,
union {
uint16_t extra; /* not further specified */
} u)
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* hash collision chain 数组中hash冲突使用*/
uint32_t cache_slot; /* cache slot (for RECV_INIT) */
uint32_t opline_num; /* opline number (for FAST_CALL) */
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 constant_flags; /* constant flags */
uint32_t extra; /* not further specified */
} u2;
};
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
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;
union u1 里面的type定义,代表的数据类型:
#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
static zend_always_inline zend_uchar zval_get_type(const zval* pz) {
return pz->u1.v.type; //使用到了u1里面的type
}
#define Z_TYPE(zval) zval_get_type(&(zval)) //获取定义变量的类型
#define Z_TYPE_P(zval_p) Z_TYPE(*(zval_p))
可以使用gdb打个断点看看PHP内核的工作情况。
新建zval.php文件
<?php
$a = 2;
echo $a;
$b = 1.1;
echo $b;
$c = null;
echo $c;
$d = true;
echo $d;
$e = "string";
echo $e;
gdb php
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-92.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/codes/bin/php...done.
(gdb) b ZEND_ECHO_SPEC_CV_HANDLER #断点位置
Breakpoint 1 at 0x9c24b9: file /root/php-7.2.31/Zend/zend_vm_execute.h, line 33112.
(gdb) r zval.php #运行zval.php文件
Starting program: /home/codes/bin/php zval.php
[Thread debugging using libthread_db enabled]
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER (execute_data=0x7ffff601e030)
at /root/php-7.2.31/Zend/zend_vm_execute.h:33112
warning: Source file is more recent than executable.
33112 USE_OPLINE
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.212.el6_10.3.x86_64 libxml2-2.7.6-21.el6_8.1.x86_64 nss-softokn-freebl-3.14.3-9.el6.x86_64 zlib-1.2.3-29.el6.x86_64
(gdb) n
33117 z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33119 if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p z
$1 = (zval *) 0x7ffff601e080
(gdb) p *z
$2 = {value = {lval = 2, dval = 9.8813129168249309e-324, counted = 0x2, str = 0x2,
arr = 0x2, obj = 0x2, res = 0x2, ref = 0x2, ast = 0x2, zv = 0x2, ptr = 0x2,
ce = 0x2, func = 0x2, ww = {w1 = 2, w2 = 0}}, u1 = {v = {type = 4 '\004',
type_flags = 0 '\000', const_flags = 0 '\000', reserved = 0 '\000'},
type_info = 4}, u2 = {next = 0, cache_slot = 0, lineno = 0, num_args = 0,
fe_pos = 0, fe_iter_idx = 0, access_flags = 0, property_guard = 0, extra = 0}}
(gdb)
这里可以看到$a的赋值lval = 2, u1里面的type = 4,接着往下走:
(gdb) c
Continuing.
2
Breakpoint 1, ZEND_ECHO_SPEC_CV_HANDLER (execute_data=0x7ffff601e030)
at /root/php-7.2.31/Zend/zend_vm_execute.h:33112
33112 USE_OPLINE
(gdb) n
33117 z = _get_zval_ptr_cv_undef(opline->op1.var EXECUTE_DATA_CC);
(gdb) n
33119 if (Z_TYPE_P(z) == IS_STRING) {
(gdb) p z
$3 = (zval *) 0x7ffff601e090
(gdb) p *z
$4 = {value = {lval = 4607632778762754458, dval = 1.1000000000000001,
counted = 0x3ff199999999999a, str = 0x3ff199999999999a, arr = 0x3ff199999999999a,
obj = 0x3ff199999999999a, res = 0x3ff199999999999a, ref = 0x3ff199999999999a,
ast = 0x3ff199999999999a, zv = 0x3ff199999999999a, ptr = 0x3ff199999999999a,
ce = 0x3ff199999999999a, func = 0x3ff199999999999a, ww = {w1 = 2576980378,
w2 = 1072798105}}, u1 = {v = {type = 5 '\005', type_flags = 0 '\000',
const_flags = 0 '\000', reserved = 0 '\000'}, type_info = 5}, u2 = {next = 0,
cache_slot = 0, lineno = 0, num_args = 0, fe_pos = 0, fe_iter_idx = 0,
access_flags = 0, property_guard = 0, extra = 0}}
可以看到$b的type = 5 double类型,赋值则为dval = 1.1000000000000001
剩下的大家可以自己执行试试。
zval内存分配
PHP5的zval分配采用的是堆上分配内存:MAKE_STD_ZVAL和ALLOC_ZVAL宏。
本来一个zval只需要24个字节, 但是算上gc_info, 其实分配了32个字节,再加上PHP自己的内存管理在分配内存的时候都会在内存前面保留一部分信息(笔记里),从而导致实际上我们只需要24字节的内存, 但最后竟然分配48个字节之多。
PHP7开始, 移除了MAKE_STD_ZVAL/ALLOC_ZVAL宏, 不再支持存堆内存上申请zval。函数内部使用的zval要么来自外面输入, 要么使用在栈上分配的临时zval。