深深明PHP内核(九)变量和数据类型-静态变量

原稿链接:http://www.orlion.ga/251/

通常静态变量是静态分配的,他们的生命周期和程序的生命周期一样长,只有在程序退出后才收生命周期,这跟组成部分变量相反,有的言语中全局变量也是静态分配的,例如PHP和js中之全局变量。

    静态变量可以分为:

  • 静态全局变量,PHP中之大局变脸也得以解呢静态全局变量,因为除开众所周知unset释放,在程序运行过程被总是。

  • 静态局部变量,也就是是当函数内部定义的静态变量,函数在执行时对变量的操作会保持到下一致次于函数被调用

  • 静态成员变量,这是以近似吃定义的静态变量,和实例变量相对应,静态成员变量可以当颇具实例中共享。

     

 

    最广的是静态局部变量和静态成员变量。局部变量只有在函数执行时才见面在。通常,当一个函数执行了,它的一对变量的价值就是都不存了,而且变量所占据的内存为深受假释,当下相同次等实行该过程的时节,所有的有的变量将重吃初始化,如果有静态变量定义也静态的,那其的价值就是无会见以函数调用结束晚释放,而是继续保留变量的值。

    本文将介绍静态局部变量

    先押一下php中一些变量的施用:

function t() {
    static $i = 0;
    $i++;
    echo $i, ' ';
}
 
t();
t();
t();

    上面的次序会输出1 2
3。从者事例可以见到$i变量的价当改动后函数继续执行还会顾到,$i变量就比如是特来函数t()才会看到之一个全局变量。那PHP是怎落实之也罢?

    static是PHP的基本点字,我们于词法分析,语法分析,中间代码生成到执行中间代码这几乎只有探讨整个经过。

 

1、词法分析

    首先查看Zend/zend_language_scanner.l文件,搜索static关键字,我们得以找到如下代码:

<ST_IN_SCRIPTING>"static" {
    return T_STATIC;
}

2、语法分析

    在词法分析找到token后,通过这token,在Zend/zend_language_parser.y文件被觅,找到代码如下:

|   T_STATIC static_var_list ';'
 
static_var_list:
        static_var_list ',' T_VARIABLE { zend_do_fetch_static_variable(&$3, 
NULL, ZEND_FETCH_STATIC TSRMLS_CC); }
    |   static_var_list ',' T_VARIABLE '=' static_scalar { 
zend_do_fetch_static_variable(&$3, &$5, ZEND_FETCH_STATIC TSRMLS_CC); }
    |   T_VARIABLE  { zend_do_fetch_static_variable(&$1, NULL, 
ZEND_FETCH_STATIC TSRMLS_CC); }
    |   T_VARIABLE '=' static_scalar { zend_do_fetch_static_variable(&$1, &$3, 
ZEND_FETCH_STATIC TSRMLS_CC); }
 
;

    语法分析的历程遭到如果配合到对应的模式则会进行相应的处理动作,通常是进展opcode的编译,在本例中的static关键字匹配中,是由于函数zend_do_fetch_static_variable处理的。

3、生成opcode中间代码

    zend_do_fetch_static_variable函数的企图就是生成opcode,定义如下:

void zend_do_fetch_static_variable(znode *varname, const znode
        *static_assignment, int fetch_type TSRMLS_DC)
{
    zval *tmp;
    zend_op *opline;
    znode lval;
    znode result;
 
    ALLOC_ZVAL(tmp);
 
    if (static_assignment) {
        *tmp = static_assignment->u.constant;
    } else {
        INIT_ZVAL(*tmp);
    }
    if (!CG(active_op_array)->static_variables) {   /* ɩ  ȐǦÁ
ʦɖĻļŴůǎ
ę */
        ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
        zend_hash_init(CG(active_op_array)->static_variables, 2, NULL, 
ZVAL_PTR_DTOR, 0);
    }
    //  8Ÿ
ʦɖĻļůė7
    zend_hash_update(CG(active_op_array)->static_variables, varname-
>u.constant.value.str.val,
        varname->u.constant.value.str.len+1, &tmp, sizeof(zval *), NULL);
 
    ...//˯ɐ
    opline = get_next_op(CG(active_op_array) TSRMLS_CC);
    opline->opcode = (fetch_type == ZEND_FETCH_LEXICAL) ? ZEND_FETCH_R : 
ZEND_FETCH_W;      /* Îwfetch_type=ZEND_FETCH_STATICJĨNǬZEND_FETCH_W*/
    opline->result.op_type = IS_VAR;
    opline->result.u.EA.type = 0;
    opline->result.u.var = get_temporary_variable(CG(active_op_array));
    opline->op1 = *varname;
    SET_UNUSED(opline->op2);
    opline->op2.u.EA.type = ZEND_FETCH_STATIC;  /* fKƣŽ%ĉˆÁNUus */
    result = opline->result;
     if (varname->op_type == IS_CONST) {
        zval_copy_ctor(&varname->u.constant);
    }
    fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact 
that the default fetch is BP_VAR_W */
 
    if (fetch_type == ZEND_FETCH_LEXICAL) {
        ...//˯ɐ
    } else {
        zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC); //  ɶŔ‰KƣŽ%
oÒ
    }
    CG(active_op_array)->opcodes[CG(active_op_array)->last-1].result.u.EA.type 
|= EXT_TYPE_UNUSED;
}

    从者的代码我们能,在解说成中间代码时,静态变量是存放于CG(active_op_array)->static_variable中的。并且转变中间代码为ZEND_FETCH_W和ZEND_ASSIGN_REF。其中ZEND_FETCH_W中间代码是以zend_do_fetch_static_variable中直接赋值,而ZEND_ASSIGN_REF中间代码是于zend_do_fetch_static_variable中调用zend_do_assign_ref生成的。

 

4、执行中间代码

    opcode的编译阶段就后虽从头opcode的行了。在Zend/zend_vm_opcodes.h文件被带有有opcode的宏定义,这些大没有特别含义,只是作为opcode的唯一标示,包含本例中相关的如下两只大的概念:

#define ZEND_FETCH_W                          83
#define ZEND_ASSIGN_REF                       39

    深入明PHP内核(三)概览-PHP脚本的履无异于和被牵线了冲opcode查找到呼应处理函数的法门,通过中间代码调用映射方法算得这ZEND_FETCH_W对应的操作为ZEND_FETCH_W_SPEC_CV_HANDLER。其代码如下:

static int ZEND_FASTCALL  
ZEND_FETCH_W_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    return zend_fetch_var_address_helper_SPEC_CV(BP_VAR_W, 
ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
}
 
static int ZEND_FASTCALL zend_fetch_var_address_helper_SPEC_CV(int type, 
ZEND_OPCODE_HANDLER_ARGS)
{
    ...// 省略
 
    if (opline->op2.u.EA.type == ZEND_FETCH_STATIC_MEMBER) {
        retval = zend_std_get_static_property(EX_T(opline-
>op2.u.var).class_entry, Z_STRVAL_P(varname), Z_STRLEN_P(varname), 0 
TSRMLS_CC);
    } else {
        // 取符号表,这里我们取得是EG(active_op_array)->static_variables
        target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), 
type, varname TSRMLS_CC);    
        ...// 省略
        if (zend_hash_find(target_symbol_table, varname->value.str.val, 
varname->value.str.len+1, (void **) &retval) == FAILURE) {
            switch (type) {
                ...// 省略
                // 在前面的调用中我们知道type = case BP_VAR_W,于是程序会走按case BP_VAR_W的流程走
                case BP_VAR_W: {
                        zval *new_zval = &EG(uninitialized_zval);
 
                        Z_ADDREF_P(new_zval);
                        zend_hash_update(target_symbol_table, varname-
>value.str.val, varname->value.str.len+1, &new_zval, sizeof(zval *), (void **) 
&retval);
                        // 更新符号表,执行赋值操作
                    }
                      break;
                EMPTY_SWITCH_DEFAULT_CASE()
            }
        }
        switch (opline->op2.u.EA.type) {
            ...// 省略
            case ZEND_FETCH_STATIC:
                zval_update_constant(retval, (void*) 1 TSRMLS_CC);
                break;
            case ZEND_FETCH_GLOBAL_LOCK:
                if (IS_CV == IS_VAR && !free_op1.var) {
                    PZVAL_LOCK(*EX_T(opline->op1.u.var).var.ptr_ptr);
                }
                break;
        }
    }
 
    ...// 省略
}

    在上面的代码中发生一个要之函数zend_get_target_symbol_table。它的打算是取当前方履行的目标符号表,而当函数执行时手上底op_array则是函数体本身,先瞧zend_op_array的结构。

struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    char *function_name;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */
 
    zend_bool done_pass_two;
 
    zend_uint *refcount;
 
    zend_op *opcodes;
    zend_uint last, size;
 
    /* static variables support */
    HashTable *static_variables;
 
    zend_op *start_op;
    int backpatch_count;
 
    zend_uint this_var;
    // ...
}

由上可以观看zend_op_array中包含function_name字段,也就是目前函数的名号。再看获取当前符号表的函数:

static inline HashTable *zend_get_target_symbol_table(const zend_op *opline, 
const temp_variable *Ts, int type, const zval *variable TSRMLS_DC)
{
    switch (opline->op2.u.EA.type) {
        ...//   ˯ɐ
        case ZEND_FETCH_STATIC:
         if (!EG(active_op_array)->static_variables) {
                ALLOC_HASHTABLE(EG(active_op_array)->static_variables);
                zend_hash_init(EG(active_op_array)->static_variables, 2, NULL, 
ZVAL_PTR_DTOR, 0);
            }
            return EG(active_op_array)->static_variables;
            break;
        EMPTY_SWITCH_DEFAULT_CASE()
    }
    return NULL;
}

于时之zend_do_fetch_static_variable执行时,op2.u.EA.type的值为ZEND_FETCH_STATIC,从而这zend_get_target_symbol_table函数我们回到的凡EG(active_op_array)->static_variable。也不怕是时函数的静态变量哈希表。每次执行时犹见面于该符号表中检索相应的价值,由于op_array以程序执行时老有。所有对静态符号表中数值的改动会继续封存,下次函数执行时继续由符号表获取信息。也就是说Zend为每个函数(准确的乃是zend_op_array)分配了一个私有的记号表来保存该函数的静态变量。

 

相关文章