内容均以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 () #define ZVAL_BOOL(z, b) do { \
zval *__z = (z); \
Z_LVAL_P(__z) = ((b) != ); \
Z_TYPE_P(__z) = IS_BOOL; \
} while () #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 () #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 () #define ZVAL_EMPTY_STRING(z) do { \
zval *__z = (z); \
Z_STRLEN_P(__z) = ; \
Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
Z_TYPE_P(__z) = IS_STRING; \
} while () #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 () #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, ); 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", );
break; case IS_BOOL:
RETVAL_STRING("boolean", );
break; case IS_LONG:
RETVAL_STRING("integer", );
break; case IS_DOUBLE:
RETVAL_STRING("double", );
break; case IS_STRING:
RETVAL_STRING("string", );
break; case IS_ARRAY:
RETVAL_STRING("array", );
break; case IS_OBJECT:
RETVAL_STRING("object", );
/*
{
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", );
break;
}
} default:
RETVAL_STRING("unknown type", );
}
}
/* }}} */

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, ); va_start(va, type_spec); // 初始化va宏,供后面va_arg()和va_end()使用
retval = zend_parse_va_args(num_args, type_spec, &va, 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 可以参考一下文档和文章:

写函数 [ Writing Functions ].md

如何在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 ( == (type_spec)[] && != __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:

#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 = ; \
(z)->is_ref__gc = ;

例:

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)) > ) { \
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 () #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", );
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)+, var, , ); \
} #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 - ); \
} \
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;
        

开发文档:https://github.com/farwish/php-core-hack

Link: http://www.cnblogs.com/farwish/p/5246126.html

[php-src]窥探Php内核中的变量的更多相关文章

  1. [php-src]窥探Php内核中的数组与面向对象

    内容均以php5.6.14为例. 扩展中定义一个类有以下四步: #1. 声明一个存储类信息的指针. zend_class_entry *errs_ce; #2. 定义方法的参数信息,类的方法实现. Z ...

  2. [php-src]理解Php内核中的函数与INI

    内容均以php-5.6.14为例. 一. 函数结构 内核中定义一个php函数使用 PHP_FUNCTION 宏 包装,扩展也不例外,该宏在 ./main/php.h:343 有着一系列类似以 PHP ...

  3. PHP内核探索之变量(4)- 数组操作

    上一节(PHP内核探索之变量(3)- hash table),我们已经知道,数组在PHP的底层实际上是HashTable(链接法解决冲突),本文将对最常用的函数系列-数组操作的相关函数做进一步的跟踪. ...

  4. PHP内核探索之变量(2)-理解引用

    本文主要内容: 引论 符号表与zval 引用原理 回到最初的问题 一.引论 很久之前写了一篇关于引用的文章,当时写的寥寥草草,很多原理都没有说清楚.最近在翻阅Derick Rethans(home: ...

  5. Linux内核中的jiffies及其作用介绍及jiffies等相关函数详解

    在LINUX的时钟中断中涉及至二个全局变量一个是xtime,它是timeval数据结构变量,另一个则是jiffies,首先看timeval结构struct timeval{time_t tv_sec; ...

  6. 操作系统---在内核中重新加载GDT和堆栈

    摘要 用BIOS方式启动计算机后,BIOS先读取引导扇区,引导扇区再从外部存储设备中读取加载器,加载器读取内核.进入内核后,把加载器中建立的GDT复制到内核中. 这篇文章的最大价值也许在末尾,对C语言 ...

  7. Linux 内核中的 Device Mapper 机制

    本文结合具体代码对 Linux 内核中的 device mapper 映射机制进行了介绍.Device mapper 是 Linux 2.6 内核中提供的一种从逻辑设备到物理设备的映射框架机制,在该机 ...

  8. PHP内核探索之变量(7)- 不平凡的字符串

    切,一个字符串有什么好研究的. 别这么说,看过<平凡的世界>么,平凡的字符串也可以有不平凡的故事.试看: (1)       在C语言中,strlen计算字符串的时间复杂度是?PHP中呢? ...

  9. PHP内核探索之变量(6)- 后续内核探索系列大纲备忘

    年前因为工作比较饱和,现在又忙着换工作的事情,基本停止了对博文的更新.后续的博文,还是慢慢补上吧. 为了不至于过于发散,先搞个未成形的大纲,如下: PHP内核探索之变量  不平凡的字符串 PHP内核探 ...

随机推荐

  1. shell 判断条件

    [ -a FILE ] 如果 FILE 存在则为真. [ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真. [ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真. [ ...

  2. 浅谈可扩展性框架:MEF

    之前在使用Prism框架时接触到了可扩展性框架MEF(Managed Extensibility Framework),体验到MEF带来的极大的便利性与可扩展性. 此篇将编写一个可组合的应用程序,帮助 ...

  3. socket测试远程地址能否连接并为连接设置超时

    public class TestConnect { string hostIp = ""; ; public string recMsg = ""; Sock ...

  4. elastic search 配置问题

    http://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html 此处有关于ES硬件规格的建议和各种推荐参数. 内存: ...

  5. python sorted和sort

    我们需要对List进行排序,Python提供了两个方法 对给定的List L进行排序, 方法1.用List的成员函数sort进行排序 方法2.用built-in函数sorted进行排序(从2.4开始) ...

  6. 多个字段用and和or时要注意用括号。

    多个字段用and和or时要注意用括号. 新技能get! create table wly_test (name1 varchar2(10),number1 number(6),score1 numbe ...

  7. Spark Streaming源码解读之生成全生命周期彻底研究与思考

    本期内容 : DStream与RDD关系彻底研究 Streaming中RDD的生成彻底研究 问题的提出 : 1. RDD是怎么生成的,依靠什么生成 2.执行时是否与Spark Core上的RDD执行有 ...

  8. Python isdigit()方法

    描述 Python isdigit() 方法检测字符串是否只由数字组成. 语法 isdigit()方法语法: str.isdigit() 参数 无. 返回值 如果字符串只包含数字则返回 True 否则 ...

  9. JavaScript Function(函数表达式)

    创建函数 创建函数的方式有两种:1.函数声明,2.函数表达式 函数声明的语法为 functionName(); //不会报错,函数声明提升function functionName(arg0,arg1 ...

  10. netstat大量time_wait连接

    http://chembo.iteye.com/blog/1503770 http://www.2cto.com/os/201007/54067.html http://blog.csdn.net/d ...