[php-src] 窥探Php内核中的变量

通过llama.cpp与羊驼聊天的网页界面- 详解 Serge 的启动使用

 

内容均以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 可以参考一下文档和文章:

写函数 [ 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 (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内核变量

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

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

posted on 2016-04-25 23:32  ercom  阅读(1108)  评论(0编辑  收藏  举报