PHP简述静态变量的含义,深入理解PHP原理之静态变量

通常意义上静态变量是静态分配的,他们的生命周期和程序的生命周期一样, 只有在程序退出时才结束期生命周期,这和局部变量相反。静态变量的类型可以分为静态全局变量、静态局部变、静态成员变量,最常见的是静态局部变量及静态成员变量,先看看如下局部变量的使用:

function t() {

static $i = 0;

$i++;

echo $i, ' ';

}

t();

t();

t();

上述的程序会输出1 2 3,从这个示例可以看出,$i变量的值在改变后函数继续执行还能访问到,变量i就像是只有函数t()才能访问到的一个全局变量,那PHP是怎么实现的呢?这个需要从词法分析,语法分析,中间代码生成到执行中间代码这几个部分探讨整个实现过程。

1.词法分析

首先查看 Zend/zend_language_scanner.l文件,搜索static关键字,我们可以找到如下代码(php7.0返回的结果有点区别,不过返回的结果其实是一样的,有兴趣的同学可以去查查):

"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编译,但是opcode编译不属于语法分析,所以语法分析可以理解为匹配该模式的过程,然后还会进行其它的处理,例如转换成简单的表达式。在本

例中的static关键字匹配中,是由函数zend_do_fetch_static_variable处理的,即由zend_do_fetch_static_variable进行opcode编译。

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 (!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);

}

// 将新的静态变量放进来

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; /* 由于fetch_type=ZEND_FETCH_STATIC,程序会选择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; /* 这在中间代码执行时会有很大作用 */

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

...//省略

}

从上面的代码我们可知,在解释成中间代码时,静态变量是存放CG(active_op_array)->static_variables中,然后opline->opcode的值为ZEND_FETCH_W,opline->op2.u.EA.type为ZEND_FETCH_STATIC,这些是执行中间代码的重要信息。

4.执行中间代码

opcode的编译阶段完成后就开始opcode的执行了, 在Zend/zend_vm_opcodes.h文件中包含所有opcode的宏定义,它们只是作为opcode的唯一表示,并没有什么含义, 下面是本例中相关的两个宏定义:

#define ZEND_FETCH_W 83

#define ZEND_ASSIGN_REF 39

根据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)

{

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) {

...//省略

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()

}

}

}

这里就不多讲,可以参考我上一篇文章《深入理解PHP原理之Global关键字》,代码逻辑是一样的,即先从target_symbol_table中查找,如果没有找到,就重新初始化,下面是查找target_symbol_table的方法zend_get_target_symbol_table()源码:

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;

}

return NULL;

}

这里和Global关键字编译过程很相似,唯一的区别是,静态变量中获取的target_symbol_table,其实是该函数中返回的EG(active_op_array)->static_variables,这是一个静态哈希表,所有对静态符号表中数值的修改会继续保留,下次函数执行时继续从该符号表获取信息,也就是说Zend为每个函数(准确的说是zend_op_array)分配了一个私有的符号表来保存该函数的静态变量。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值