zoukankan      html  css  js  c++  java
  • [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 可以参考一下文档和文章:

    写函数 [ 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
    ");
            php_printf("%x", Z_STRVAL_P(var));
        } else {
            php_printf("没有发现$foo
    ");
        }
    }

    如果 $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

  • 相关阅读:
    「BZOJ1954」Pku3764 The xor – longest Path
    【bzoj4260】【Codechef REBXOR】
    BZOJ_3012_[Usaco2012 Dec]First!
    【bzoj1174】[Balkan2007]Toponyms
    String
    前缀和
    [POI2008] CLO
    [Scoi2010] 游戏
    CodeForces892E
    并查集的删除操作
  • 原文地址:https://www.cnblogs.com/farwish/p/5246126.html
Copyright © 2011-2022 走看看