深入理解PHP原理之静态变量
2016-08-28 10:42
846 查看
通常意义上静态变量是静态分配的,他们的生命周期和程序的生命周期一样, 只有在程序退出时才结束期生命周期,这和局部变量相反。静态变量的类型可以分为静态全局变量、静态局部变、静态成员变量,最常见的是静态局部变量及静态成员变量,先看看如下局部变量的使用:
上述的程序会输出1 2 3,从这个示例可以看出,$i变量的值在改变后函数继续执行还能访问到,变量i就像是只有函数t()才能访问到的一个全局变量,那PHP是怎么实现的呢?这个需要从词法分析,语法分析,中间代码生成到执行中间代码这几个部分探讨整个实现过程。
1.词法分析
首先查看 Zend/zend_language_scanner.l文件,搜索static关键字,我们可以找到如下代码(php7.0返回的结果有点区别,不过返回的结果其实是一样的,有兴趣的同学可以去查查):
2.语法分析
在词法分析找到token后,通过这个token在Zend/zend_language_parser.y文件中查找,找到相关代码如下:
语法分析的过程中如果匹配到相应的模式则会进行相应的处理动作,该操作一般是进行opcode编译,但是opcode编译不属于语法分析,所以语法分析可以理解为匹配该模式的过程,然后还会进行其它的处理,例如转换成简单的表达式。在本
例中的static关键字匹配中,是由函数zend_do_fetch_static_variable处理的,即由zend_do_fetch_static_variable进行opcode编译。
3.生成opcode中间代码
zend_do_fetch_static_variable函数的作用就是生成opcode,定义如下:
从上面的代码我们可知,在解释成中间代码时,静态变量是存放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的唯一表示,并没有什么含义, 下面是本例中相关的两个宏定义:
根据opcode查找到相应处理函数,即通过中间代码调用映射方法计算得此时ZEND_FETCH_W对应的操作ZEND_FETCH_W_SPEC_CV_HANDLER,其代码如下:
这里就不多讲,可以参考我上一篇文章《深入理解PHP原理之Global关键字》,代码逻辑是一样的,即先从target_symbol_table中查找,如果没有找到,就重新初始化,下面是查找target_symbol_table的方法zend_get_target_symbol_table()源码:
这里和Global关键字编译过程很相似,唯一的区别是,静态变量中获取的target_symbol_table,其实是该函数中返回的EG(active_op_array)->static_variables,这是一个静态哈希表,所有对静态符号表中数值的修改会继续保留,下次函数执行时继续从该符号表获取信息,也就是说Zend为每个函数(准确的说是zend_op_array)分配了一个私有的符号表来保存该函数的静态变量。
参考: http://www.php-internals.com/
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返回的结果有点区别,不过返回的结果其实是一样的,有兴趣的同学可以去查查):
<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编译,但是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)分配了一个私有的符号表来保存该函数的静态变量。
参考: http://www.php-internals.com/
相关文章推荐
- 深入理解PHP原理之变量声明
- 深入理解PHP原理之变量赋值
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量分离/引用(Variables Separation)
- 深入理解PHP原理之变量 【转自(风雪之隅)】
- 深入理解PHP原理之变量作用域(Scope in PHP)
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量分离/引用(Variables Separation)
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量作用域(Scope in PHP)
- [李景山php] 深入理解PHP内核[读书笔记]--第三章:变量及数据类型--变量的结构和类型--静态变量
- 深入理解PHP原理之变量(Variables inside PHP)
- 深入理解PHP原理之变量作用域(Scope in PHP)
- 深入理解PHP原理之变量作用域
- 深入理解PHP原理之变量作用域(Scope in PHP)
- 深入理解PHP原理之变量生命期(一)
- 深入理解PHP原理之变量结构
- 深入理解PHP原理之变量分离/引用(Variables Separation)
- 深入理解PHP原理之变量分离/引用(Variables Separation)