[php-src] 窥探Php内核中的变量
内容均以php-5.6.14为例.
在看各种组合数据类型之前,有必要先熟悉下 Zend/zend_types.h 里面的自定义数据类型.
#ifndef ZEND_TYPES_H // 防止多次 include 头文件导致预处理错误 #define ZEND_TYPES_H typedef unsigned char zend_bool; typedef unsigned char zend_uchar; typedef unsigned int zend_uint; typedef unsigned long zend_ulong; typedef unsigned short zend_ushort; #define HAVE_ZEND_LONG64 // 如果最后 else 执行了 #undef HAVE_ZEND_LONG64,说明中间的定义无效,否则表示有效 #ifdef ZEND_WIN32 // 条件1:下面对 window32 环境处理 typedef __int64 zend_long64; // 定义有符号64位整数类型(相当于 bigint 或 long long)的别名 zend_long64 typedef unsigned __int64 zend_ulong64; // 无符号64位整数类型别名 zend_ulong64 #elif SIZEOF_LONG_LONG_INT == 8 // 条件2:在 main/php_config.h 2351行定义,如果成立则继续 typedef long long int zend_long64; // 别名同上 typedef unsigned long long int zend_ulong64; // 别名同上 #elif SIZEOF_LONG_LONG == 8 // 条件3 typedef long long zend_long64; typedef unsigned long long zend_ulong64; #else # undef HAVE_ZEND_LONG64 // 如果上面条件都不满足,则取消 HAVE_ZEND_LONG64 全局定义 #endif #ifdef _WIN64 // 如果是 windows 平台64位VC编程,适用以下别名 typedef __int64 zend_intptr_t; typedef unsigned __int64 zend_uintptr_t; #else typedef long zend_intptr_t; typedef unsigned long zend_uintptr_t; #endif // _zend_object_handlers 结构体在 Zend/zend_object_handlers.h 第119行 // _zval_struct 结构体是在 Zend/zend.h 第334行,内核中php变量的形式 typedef unsigned int zend_object_handle; typedef struct _zend_object_handlers zend_object_handlers; typedef struct _zval_struct zval; // 定义一个含上面成员的结构体 zend_object_value,其它地方多处用到 typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value; #endif /* ZEND_TYPES_H */
_zval_struct 是一个巧妙的结构体:
/* * zval */ typedef struct _zend_class_entry zend_class_entry; typedef struct _zend_guard { zend_bool in_get; zend_bool in_set; zend_bool in_unset; zend_bool in_isset; zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */ } zend_guard; typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; /* protects from __get/__set ... recursion */ } zend_object; #include "zend_object_handlers.h" #include "zend_ast.h" typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; // 对象 zend_ast *ast; } zvalue_value; struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; };
_zend_class_entry 是一个复杂的结构体,用以实现 php 的面向对象,以后再说.
_zval_struct 中 zvalue_value 联合体是真正存储数据的地方,type 表示变量类型,refcount__gc 表示zval使用的数量,is_ref__gc 表示使用是否是&变量引用(0或1).
zval_debug_dump() 函数用来查看refcount的值,这里有几个令人迷惑的概念:
( php中如果使用$a = &$b,表示两个变量其实是一个,改变任何一个变量值,两个都变. )
// $var1 占用1个refcount,函数传值复制占用1个refcount $var1 = 'hello'; debug_zval_dump($var1); // string(11) "hello" refcount(2) // 同上,再加上$var2占用1个refcount $var1 = 'hello'; $var2 = $var1; debug_zval_dump($var1); // string(11) "hello" refcount(3) $var1 = 'hello'; // refcount=1, is_ref=0 $var2 = &$var1; // refcount=1, is_ref=1 debug_zval_dump($var1); // string(11) "hello" refcount(1) // 这里暂且理解为变量引用使refcount=1
在 zval 的基础上,php实现8种数据类型,它们的常量名称分别是:IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE;额外的常量帮助指定内部类型,如 常量数组和可调用的对象.
它们在 Zend/zend.h:581
/* data types */ /* All data types <= IS_BOOL have their constructor/destructors skipped */ #define IS_NULL 0 #define IS_LONG 1 #define IS_DOUBLE 2 #define IS_BOOL 3 #define IS_ARRAY 4 #define IS_OBJECT 5 #define IS_STRING 6 #define IS_RESOURCE 7 #define IS_CONSTANT 8 #define IS_CONSTANT_AST 9 #define IS_CALLABLE 10 #define IS_CONSTANT_TYPE_MASK 0x00f #define IS_CONSTANT_UNQUALIFIED 0x010 #define IS_LEXICAL_VAR 0x020 #define IS_LEXICAL_REF 0x040 #define IS_CONSTANT_IN_NAMESPACE 0x100 #define IS_CONSTANT_TYPE(type) (((type) & IS_CONSTANT_TYPE_MASK) >= IS_CONSTANT && ((type) & IS_CONSTANT_TYPE_MASK) <= IS_CONSTANT_AST)
zval 的成员 type 就是8种当中的一个,由于内核提供一系列设置变量类型的宏,所以就不建议直接使用 zval.type = IS_LONG; zval.value.lval = 10 这种方式;
那么如何设置 zval 的类型,内核提供三种方式,在 Zend/zend_operators.h:489
#define Z_TYPE(zval) (zval).type #define Z_TYPE_P(zval_p) Z_TYPE(*zval_p) #define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)
Z_TYPE 对应 zval 结构体的实体,Z_TYPE_P(zval_p) 对应 zval 结构体的指针,Z_TYPE_PP(zval_pp) 两个P就是对应 zval 结构体的二级指针了;使用哪个取决于你的参数是 zval 或 zval * 或 zval ** .
另一种方便设置 zval 类型的宏 ZVAL_* 系列在 ./Zend/zend_API.h:549
#define ZVAL_RESOURCE(z, l) do { \ zval *__z = (z); \ Z_LVAL_P(__z) = l; \ Z_TYPE_P(__z) = IS_RESOURCE;\ } while (0) #define ZVAL_BOOL(z, b) do { \ zval *__z = (z); \ Z_LVAL_P(__z) = ((b) != 0); \ Z_TYPE_P(__z) = IS_BOOL; \ } while (0) #define ZVAL_NULL(z) { \ Z_TYPE_P(z) = IS_NULL; \ } #define ZVAL_LONG(z, l) { \ zval *__z = (z); \ Z_LVAL_P(__z) = l; \ Z_TYPE_P(__z) = IS_LONG; \ } #define ZVAL_DOUBLE(z, d) { \ zval *__z = (z); \ Z_DVAL_P(__z) = d; \ Z_TYPE_P(__z) = IS_DOUBLE; \ } #define ZVAL_STRING(z, s, duplicate) do { \ const char *__s=(s); \ zval *__z = (z); \ Z_STRLEN_P(__z) = strlen(__s); \ Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\ Z_TYPE_P(__z) = IS_STRING; \ } while (0) #define ZVAL_STRINGL(z, s, l, duplicate) do { \ const char *__s=(s); int __l=l; \ zval *__z = (z); \ Z_STRLEN_P(__z) = __l; \ Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\ Z_TYPE_P(__z) = IS_STRING; \ } while (0) #define ZVAL_EMPTY_STRING(z) do { \ zval *__z = (z); \ Z_STRLEN_P(__z) = 0; \ Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\ Z_TYPE_P(__z) = IS_STRING; \ } while (0) #define ZVAL_ZVAL(z, zv, copy, dtor) do { \ zval *__z = (z); \ zval *__zv = (zv); \ ZVAL_COPY_VALUE(__z, __zv); \ if (copy) { \ zval_copy_ctor(__z); \ } \ if (dtor) { \ if (!copy) { \ ZVAL_NULL(__zv); \ } \ zval_ptr_dtor(&__zv); \ } \ } while (0) #define ZVAL_FALSE(z) ZVAL_BOOL(z, 0) #define ZVAL_TRUE(z) ZVAL_BOOL(z, 1)
注意 zval_ptr_dtor(&var) 传 zval *var 的地址,销毁一个变量;如果是 zval **var,用 zval_dtor(&var)。
./Zend/zend_variables.h:55
ZEND_API void _zval_dtor_func(zval *zvalue ZEND_FILE_LINE_DC); static zend_always_inline void _zval_dtor(zval *zvalue ZEND_FILE_LINE_DC) { if (zvalue->type <= IS_BOOL) { return; } _zval_dtor_func(zvalue ZEND_FILE_LINE_RELAY_CC); } ZEND_API void _zval_copy_ctor_func(zval *zvalue ZEND_FILE_LINE_DC); static zend_always_inline void _zval_copy_ctor(zval *zvalue ZEND_FILE_LINE_DC) { if (zvalue->type <= IS_BOOL) { return; } _zval_copy_ctor_func(zvalue ZEND_FILE_LINE_RELAY_CC); } .... #define zval_copy_ctor(zvalue) _zval_copy_ctor((zvalue) ZEND_FILE_LINE_CC) #define zval_dtor(zvalue) _zval_dtor((zvalue) ZEND_FILE_LINE_CC) #define zval_ptr_dtor(zval_ptr) _zval_ptr_dtor((zval_ptr) ZEND_FILE_LINE_CC)
./Zend/zend_variables.c:31
ZEND_API void _zval_dtor_func(zval *zvalue ZEND_FILE_LINE_DC) { switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) { case IS_STRING: case IS_CONSTANT: CHECK_ZVAL_STRING_REL(zvalue); str_efree_rel(zvalue->value.str.val); break; case IS_ARRAY: { TSRMLS_FETCH(); if (zvalue->value.ht && (zvalue->value.ht != &EG(symbol_table))) { /* break possible cycles */ Z_TYPE_P(zvalue) = IS_NULL; zend_hash_destroy(zvalue->value.ht); FREE_HASHTABLE(zvalue->value.ht); } } break; case IS_CONSTANT_AST: zend_ast_destroy(Z_AST_P(zvalue)); break; case IS_OBJECT: { TSRMLS_FETCH(); Z_OBJ_HT_P(zvalue)->del_ref(zvalue TSRMLS_CC); } break; case IS_RESOURCE: { TSRMLS_FETCH(); /* destroy resource */ zend_list_delete(zvalue->value.lval); } break; case IS_LONG: case IS_DOUBLE: case IS_BOOL: case IS_NULL: default: return; break; } } .... ZEND_API void _zval_copy_ctor_func(zval *zvalue ZEND_FILE_LINE_DC) { switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) { case IS_RESOURCE: { TSRMLS_FETCH(); zend_list_addref(zvalue->value.lval); } break; case IS_BOOL: case IS_LONG: case IS_NULL: break; case IS_CONSTANT: case IS_STRING: CHECK_ZVAL_STRING_REL(zvalue); if (!IS_INTERNED(zvalue->value.str.val)) { zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value. str.len); } break; case IS_ARRAY: { zval *tmp; HashTable *original_ht = zvalue->value.ht; HashTable *tmp_ht = NULL; TSRMLS_FETCH(); if (zvalue->value.ht == &EG(symbol_table)) { return; /* do nothing */ } ALLOC_HASHTABLE_REL(tmp_ht); zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 0); zvalue->value.ht = tmp_ht; zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) zval_add_ref, (void *) &tmp, sizeof(zval *)); tmp_ht->nNextFreeElement = original_ht->nNextFreeElement; } break; case IS_CONSTANT_AST: Z_AST_P(zvalue) = zend_ast_copy(Z_AST_P(zvalue)); break; case IS_OBJECT: { TSRMLS_FETCH(); Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC); } break; } }
类型有了,设置每种类型的变量值 内核也提供三种方式,在 Zend/zend_operators.h:441
#define Z_LVAL(zval) (zval).value.lval #define Z_BVAL(zval) ((zend_bool)(zval).value.lval) #define Z_DVAL(zval) (zval).value.dval #define Z_STRVAL(zval) (zval).value.str.val #define Z_STRLEN(zval) (zval).value.str.len #define Z_ARRVAL(zval) (zval).value.ht #define Z_AST(zval) (zval).value.ast #define Z_OBJVAL(zval) (zval).value.obj #define Z_OBJ_HANDLE(zval) Z_OBJVAL(zval).handle #define Z_OBJ_HT(zval) Z_OBJVAL(zval).handlers #define Z_OBJCE(zval) zend_get_class_entry(&(zval) TSRMLS_CC) #define Z_OBJPROP(zval) Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC) #define Z_OBJ_HANDLER(zval, hf) Z_OBJ_HT((zval))->hf #define Z_RESVAL(zval) (zval).value.lval #define Z_OBJDEBUG(zval,is_tmp) (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval), get_debug_info)(&(zval),&is_tmp TSRMLS_CC):(is_tmp=0,Z_OBJ_HANDLER((zval),get_properties)? Z_OBJPROP(zval):NULL))
#define Z_LVAL_P(zval_p) Z_LVAL(*zval_p) #define Z_BVAL_P(zval_p) Z_BVAL(*zval_p) #define Z_DVAL_P(zval_p) Z_DVAL(*zval_p) #define Z_STRVAL_P(zval_p) Z_STRVAL(*zval_p) #define Z_STRLEN_P(zval_p) Z_STRLEN(*zval_p) #define Z_ARRVAL_P(zval_p) Z_ARRVAL(*zval_p) #define Z_AST_P(zval_p) Z_AST(*zval_p) #define Z_OBJPROP_P(zval_p) Z_OBJPROP(*zval_p) #define Z_OBJCE_P(zval_p) Z_OBJCE(*zval_p) #define Z_RESVAL_P(zval_p) Z_RESVAL(*zval_p) #define Z_OBJVAL_P(zval_p) Z_OBJVAL(*zval_p) #define Z_OBJ_HANDLE_P(zval_p) Z_OBJ_HANDLE(*zval_p) #define Z_OBJ_HT_P(zval_p) Z_OBJ_HT(*zval_p) #define Z_OBJ_HANDLER_P(zval_p, h) Z_OBJ_HANDLER(*zval_p, h) #define Z_OBJDEBUG_P(zval_p,is_tmp) Z_OBJDEBUG(*zval_p,is_tmp)
#define Z_LVAL_PP(zval_pp) Z_LVAL(**zval_pp) #define Z_BVAL_PP(zval_pp) Z_BVAL(**zval_pp) #define Z_DVAL_PP(zval_pp) Z_DVAL(**zval_pp) #define Z_STRVAL_PP(zval_pp) Z_STRVAL(**zval_pp) #define Z_STRLEN_PP(zval_pp) Z_STRLEN(**zval_pp) #define Z_ARRVAL_PP(zval_pp) Z_ARRVAL(**zval_pp) #define Z_AST_PP(zval_p) Z_AST(**zval_p) #define Z_OBJPROP_PP(zval_pp) Z_OBJPROP(**zval_pp) #define Z_OBJCE_PP(zval_pp) Z_OBJCE(**zval_pp) #define Z_RESVAL_PP(zval_pp) Z_RESVAL(**zval_pp) #define Z_OBJVAL_PP(zval_pp) Z_OBJVAL(**zval_pp) #define Z_OBJ_HANDLE_PP(zval_p) Z_OBJ_HANDLE(**zval_p) #define Z_OBJ_HT_PP(zval_p) Z_OBJ_HT(**zval_p) #define Z_OBJ_HANDLER_PP(zval_p, h) Z_OBJ_HANDLER(**zval_p, h) #define Z_OBJDEBUG_PP(zval_pp,is_tmp) Z_OBJDEBUG(**zval_pp,is_tmp)
利用这些类型宏函数,看看常用的php函数是如何实现的:
ext/standard/php_type.h
#ifndef PHP_TYPE_H #define PHP_TYPE_H PHP_FUNCTION(intval); PHP_FUNCTION(floatval); PHP_FUNCTION(strval); PHP_FUNCTION(boolval); PHP_FUNCTION(gettype); PHP_FUNCTION(settype); PHP_FUNCTION(is_null); PHP_FUNCTION(is_resource); PHP_FUNCTION(is_bool); PHP_FUNCTION(is_long); PHP_FUNCTION(is_float); PHP_FUNCTION(is_numeric); PHP_FUNCTION(is_string); PHP_FUNCTION(is_array); PHP_FUNCTION(is_object); PHP_FUNCTION(is_scalar); PHP_FUNCTION(is_callable); #endif
ext/standard/type.c
/* {{{ proto string gettype(mixed var) Returns the type of the variable */ PHP_FUNCTION(gettype) { zval **arg; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) { return; } switch (Z_TYPE_PP(arg)) { case IS_NULL: RETVAL_STRING("NULL", 1); break; case IS_BOOL: RETVAL_STRING("boolean", 1); break; case IS_LONG: RETVAL_STRING("integer", 1); break; case IS_DOUBLE: RETVAL_STRING("double", 1); break; case IS_STRING: RETVAL_STRING("string", 1); break; case IS_ARRAY: RETVAL_STRING("array", 1); break; case IS_OBJECT: RETVAL_STRING("object", 1); /* { char *result; int res_len; res_len = sizeof("object of type ")-1 + Z_OBJCE_P(arg)->name_length; spprintf(&result, 0, "object of type %s", Z_OBJCE_P(arg)->name); RETVAL_STRINGL(result, res_len, 0); } */ break; case IS_RESOURCE: { const char *type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC); if (type_name) { RETVAL_STRING("resource", 1); break; } } default: RETVAL_STRING("unknown type", 1); } } /* }}} */
ZEND_NUM_ARGS() 就是下面这玩意儿,表示传递参数的数量,Zend/zend_API.h:355
#define ZEND_NUM_ARGS() (ht)
zend_parse_parameters() 在 Zend/zend_API.h:257,获取函数使用者传递的参数:
/* internal function to efficiently copy parameters when executing __call() */ ZEND_API int zend_copy_parameters_array(int param_count, zval *argument_array TSRMLS_DC); #define zend_get_parameters_array(ht, param_count, argument_array) \ _zend_get_parameters_array(ht, param_count, argument_array TSRMLS_CC) #define zend_get_parameters_array_ex(param_count, argument_array) \ _zend_get_parameters_array_ex(param_count, argument_array TSRMLS_CC) #define zend_parse_parameters_none() \ zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") /* Parameter parsing API -- andrei */ #define ZEND_PARSE_PARAMS_QUIET 1<<1 ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, ...); ZEND_API int zend_parse_parameters_ex(int flags, int num_args TSRMLS_DC, const char *type_spec, ...); ZEND_API char *zend_zval_type_name(const zval *arg); ZEND_API int zend_parse_method_parameters(int num_args TSRMLS_DC, zval *this_ptr, const char *type_spec, ...); ZEND_API int zend_parse_method_parameters_ex(int flags, int num_args TSRMLS_DC, zval *this_ptr, const char *type_spec, ...); ZEND_API int zend_parse_parameter(int flags, int arg_num TSRMLS_DC, zval **arg, const char *spec, ...); /* End of parameter parsing API -- andrei */
zend_parse_parameters_ex 是 zend_parse_parameters 的扩展版本,可以多传一个参数 flags,它的值只能是 ZEND_PARSE_PARAMS_QUIET,用来标识在运行时不输出任何错误信息。
这对于需要传一组完全不同参数的函数来说是有用的,但你需要输出自己的错误信息。
例:
long l1, l2, l3; char *s; if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, "lll", &l1, &l2, &l3) == SUCCESS) { /* manipulate longs */ } else if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "s", &s, &s_len) == SUCCESS) { /* manipulate string */ } else { php_error(E_WARNING, "%s() takes either three long values or a string as argument", get_active_function_name(TSRMLS_C)); return; }
zend_parse_parameters 实现 zend_API.c:916
ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, ...) /* {{{ */ { va_list va; // stdarg.h 中解决可变参数的宏 int retval; RETURN_IF_ZERO_ARGS(num_args, type_spec, 0); va_start(va, type_spec); // 初始化va宏,供后面va_arg()和va_end()使用 retval = zend_parse_va_args(num_args, type_spec, &va, 0 TSRMLS_CC); // Zend/zend_API.c 第729行实现 va_end(va); // 调用 va_end(va) 后,va 会变成 undefined return retval; }
注意,第二个参数一定是用双引号包裹,否则编译不通过;所以以防万一,以后在扩展中我们尽量都用双引号。
zend_parse_parameters_array_ex() 用于把传递给函数的参数填充到第二个参数中,第一个参数传数量。
关于 zend_parse_parameters 的 type_spec 可以参考一下文档和文章:
如何在Zend Api中对参数进行获取:zend_parse_parameters()和zend_parse_parameters_ex()
下面是截取的一张图:
其中 l 和 L 效果一样,p 和 s 一样,A 和 a 一样,H 和 h 一样;
具体实现在 ./Zend/zend_API.c:305:static const char *zend_parse_arg_impl()
/ : 前面参数如果不是引用,提前进行一份拷贝,方便函数内随意操作.
! : 前面的参数未初始化时设为 null,减少 IS_NULL 类型的 zval 的内存占用 .
RETURN_IF_ZERO_ARGS() 在 zend_API.c:888,检测到没有参数时报Warning的宏:
#define RETURN_IF_ZERO_ARGS(num_args, type_spec, quiet) { \ int __num_args = (num_args); \ \ if (0 == (type_spec)[0] && 0 != __num_args && !(quiet)) { \ const char *__space; \ const char * __class_name = get_active_class_name(&__space TSRMLS_CC); \ zend_error(E_WARNING, "%s%s%s() expects exactly 0 parameters, %d given", \ __class_name, __space, \ get_active_function_name(TSRMLS_C), __num_args); \ return FAILURE; \ }\ }
函数返回值的宏 ./Zend/zend_API.h:618,RETURN_*宏 为 RETVAL_*宏 自动补上了 return.
#define ZVAL_FALSE(z) ZVAL_BOOL(z, 0) #define ZVAL_TRUE(z) ZVAL_BOOL(z, 1) #define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l) #define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b) #define RETVAL_NULL() ZVAL_NULL(return_value) #define RETVAL_LONG(l) ZVAL_LONG(return_value, l) #define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d) #define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)//duplicate表示是否需要复制,1就复制 #define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)//知道字符长度的时候使用,效率比上一个高 #define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value) #define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor) #define RETVAL_FALSE ZVAL_BOOL(return_value, 0) #define RETVAL_TRUE ZVAL_BOOL(return_value, 1) #define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; } #define RETURN_BOOL(b) { RETVAL_BOOL(b); return; } #define RETURN_NULL() { RETVAL_NULL(); return;} #define RETURN_LONG(l) { RETVAL_LONG(l); return; } #define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; } #define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; } #define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; } #define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; } #define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; } #define RETURN_FALSE { RETVAL_FALSE; return; } #define RETURN_TRUE { RETVAL_TRUE; return; }
创建 zval 变量:
MAKE_STD_ZVAL() 需要传入一个 zval 指针:
./Zend/zend.h:760 #define MAKE_STD_ZVAL(zv) \ ALLOC_ZVAL(zv); \ INIT_PZVAL(zv);
./Zend/zend_alloc.h:165 分配zval大小的内存空间 /* fast cache for zval's */ #define ALLOC_ZVAL(z) \ (z) = (zval *) emalloc(sizeof(zval))
./Zend/zend.h:750 初始化zval #define INIT_PZVAL(z) \ (z)->refcount__gc = 1; \ (z)->is_ref__gc = 0;
例:
zval *new_variable; // 分配并初始化zval MAKE_STD_ZVAL(new_variable);
Z_TYPE_P(new_variable) = IS_STRING; 设置类型和值 // 变量以“new_variable_name”的名字加入符号表,可以使用 $new_variable_name 访问 ZEND_SET_SYMBOL(EG(active_symbol_table), "new_variable_name", new_variable);
变量类型转换:
ZEND_API void convert_scalar_to_number(zval *op TSRMLS_DC); ZEND_API void _convert_to_cstring(zval *op ZEND_FILE_LINE_DC); ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC); ZEND_API void convert_to_long(zval *op); ZEND_API void convert_to_double(zval *op); ZEND_API void convert_to_long_base(zval *op, int base); ZEND_API void convert_to_null(zval *op); ZEND_API void convert_to_boolean(zval *op); ZEND_API void convert_to_array(zval *op); ZEND_API void convert_to_object(zval *op); ZEND_API void multi_convert_to_long_ex(int argc, ...); ZEND_API void multi_convert_to_double_ex(int argc, ...); ZEND_API void multi_convert_to_string_ex(int argc, ...); ZEND_API int add_char_to_string(zval *result, const zval *op1, const zval *op2); ZEND_API int add_string_to_string(zval *result, const zval *op1, const zval *op2); #define convert_to_cstring(op) if ((op)->type != IS_STRING) { _convert_to_cstring((op) ZEND_FILE_LINE_CC); } #define convert_to_string(op) if ((op)->type != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); }
下面是 convert_to_* 系列的扩展函数,不会破坏原数据,防止有其它人在使用初始zval的情况,里面用 SEPARATE_ZVAL_IF_NOT_REF 保证分离出一个zval,引用传递时可写,非引用传递时只读,PZVAL_IS_REF(zval*)可检测参数是否是一个引用;
所有的转换函数传递一个 **zval 作为参数,见 ./Zend/zend_operators.h:427
#define convert_to_boolean_ex(ppzv) convert_to_ex_master(ppzv, boolean, BOOL) #define convert_to_long_ex(ppzv) convert_to_ex_master(ppzv, long, LONG) #define convert_to_double_ex(ppzv) convert_to_ex_master(ppzv, double, DOUBLE) #define convert_to_string_ex(ppzv) convert_to_ex_master(ppzv, string, STRING) #define convert_to_array_ex(ppzv) convert_to_ex_master(ppzv, array, ARRAY) #define convert_to_object_ex(ppzv) convert_to_ex_master(ppzv, object, OBJECT) #define convert_to_null_ex(ppzv) convert_to_ex_master(ppzv, null, NULL)
.....
#define convert_to_ex_master(ppzv, lower_type, upper_type) \ if (Z_TYPE_PP(ppzv)!=IS_##upper_type) { \ SEPARATE_ZVAL_IF_NOT_REF(ppzv); \ convert_to_##lower_type(*ppzv); \ }
注意这里没有 convert_to_resource(),因为用户层面不会直接操作资源,所以内核不提供转换。
./Zend/zend.h:791 SEPARATE_ZVAL_IF_NOT_REF #define SEPARATE_ZVAL(ppzv) \ do { \ if (Z_REFCOUNT_PP((ppzv)) > 1) { \ zval *new_zv; \ Z_DELREF_PP(ppzv); \ ALLOC_ZVAL(new_zv); \ INIT_PZVAL_COPY(new_zv, *(ppzv)); \ *(ppzv) = new_zv; \ zval_copy_ctor(new_zv); \ } \ } while (0) #define SEPARATE_ZVAL_IF_NOT_REF(ppzv) \ if (!PZVAL_IS_REF(*ppzv)) { \ SEPARATE_ZVAL(ppzv); \ }
变量存储:
./Zend/zend_globals.h 中定义了全局参数,_zend_compiler_globals 是编译时就固定的,_zend_executor_globals 结构内参数是执行时决定的;symbol_table 是全局作用域符号表,用 EG(symbol_table)访问,类似 $GLOBALS,active_symbol_table 是当前作用域符号表,用 EG(active_symbol_table)访问。
在内核中创建变量,使之在用户层可用:
1、创建并初始化zval
2、设置zval的值
3、加入当前符号表
zval *var; MAKE_STD_ZVAL(var); ZVAL_STRING(var, "this is my global variable", 1); ZEND_SET_SYMBOL( EG(active_symbol_table), "myvar", var);
<?php echo $myvar; // this is my global variable
设置符号表的定义在 ./Zend/zend_API.h:691
#define ZEND_SET_SYMBOL(symtable, name, var) \ { \ char *_name = (name); \ \ ZEND_SET_SYMBOL_WITH_LENGTH(symtable, _name, strlen(_name)+1, var, 1, 0); \ } #define ZEND_SET_SYMBOL_WITH_LENGTH(symtable, name, name_length, var, _refcount, _is_ref) \ { \ zval **orig_var; \ \ if (zend_hash_find(symtable, (name), (name_length), (void **) &orig_var)==SUCCESS \ && PZVAL_IS_REF(*orig_var)) { \ Z_SET_REFCOUNT_P(var, Z_REFCOUNT_PP(orig_var)); \ Z_SET_ISREF_P(var); \ \ if (_refcount) { \ Z_SET_REFCOUNT_P(var, Z_REFCOUNT_P(var) + _refcount - 1); \ } \ zval_dtor(*orig_var); \ **orig_var = *(var); \ FREE_ZVAL(var); \ } else { \ Z_SET_ISREF_TO_P(var, _is_ref); \ if (_refcount) { \ Z_SET_REFCOUNT_P(var, _refcount); \ } \ zend_hash_update(symtable, (name), (name_length), &(var), sizeof(zval *), NULL); \ } \ }
从上面我们可以看出 zend_hash_find 与 ZEND_SET_SYMBOL 的关系:
调用 ZEND_SET_SYMBOL(),内部会先用 zend_hash_find() 进行查找这个变量是否已经在符号表中。
变量检索(zend_hash_find):
{ zval *var; if (zend_hash_find( EG(active_symbol_table), "foo", sizeof("foo"), (void **)&var ) == SUCCESS) { php_printf("发现$foo\n"); php_printf("%x", Z_STRVAL_P(var)); } else { php_printf("没有发现$foo\n"); } }
如果 $foo 存在于当前作用域中,返回 SUCCESS,并把 $foo 的地址赋给 var;不存在时返回 FAILURE,var 的值不更改。
./Zend/zend_hash.h:164
/* Data retreival */ ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData);
./Zend/zend_hash.c:838
/* Returns SUCCESS if found and FAILURE if not. The pointer to the * data is returned in pData. The reason is that there's no reason * someone using the hash table might not want to have NULL data */ ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) { ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if (p->arKey == arKey || ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) { *pData = p->pData; return SUCCESS; } p = p->pNext; } return FAILURE; }
总结一下变量相关宏之间的关系:(具体实现可以看上面的代码片段)
RETURN_STRING(s, duplicate) 函数返回时使用,RETVAL_STRING() 结尾加上 return 的版本.
|_
RETVAL_STRING(s, duplicate) 函数返回时使用,ZVAL_STRING() 的精简版本,默认返回值是 return_value.
|_
ZVAL_STRING(z, s, duplicate) 设置值时使用,为 z 赋值,相当于调用了下面的宏.
|_
Z_STRLEN_P(z) = strlen((char *)s);
Z_STRVAL_P(z) = (char *)s;
Z_TYPE_P(z) = IS_STRING;
Refer:什么是PHP内核变量