局部变量通过编译时确定的编号进行读写操作,而静态变量通过哈希表保存,这就使得其不能像普通变量那样有一个固定的编号,有一种可能是通过变量名索引的,那么究竟是否如此呢?我们分析下其编译过程。
静态变量编译的语法规则:
statement:
...
| T_STATIC static_var_list ';' { $$ = $2; }
...
;
static_var_list:
static_var_list ',' static_var { $$ = zend_ast_list_add($1, $3); }
| static_var { $$ = zend_ast_create_list(1, ZEND_AST_STMT_LIST, $1); }
;
static_var:
T_VARIABLE { $$ = zend_ast_create(ZEND_AST_STATIC, $1, NULL); }
| T_VARIABLE '=' expr { $$ = zend_ast_create(ZEND_AST_STATIC, $1, $3); }
;
语法解析后生成了一个ZEND_AST_STATIC
语法树节点,接着再看下这个节点编译为opcode的过程:zend_compile_static_var。
void zend_compile_static_var(zend_ast *ast)
{
zend_ast *var_ast = ast->child[0];
zend_ast *value_ast = ast->child[1];
zval value_zv;
if (value_ast) {
//定义了初始值
zend_const_expr_to_zval(&value_zv, value_ast);
} else {
//无初始值
ZVAL_NULL(&value_zv);
}
zend_compile_static_var_common(var_ast, &value_zv, 1);
}
这里首先对初始化值进行编译,最终得到一个固定值,然后调用:zend_compile_static_var_common()
处理,首先判断当前编译的zend_op_array->static_variables
是否已创建,未创建则分配一个HashTable,接着将定义的静态变量插入:
//zend_compile_static_var_common():
if (!CG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 8, NULL, ZVAL_PTR_DTOR, 0);
}
//插入静态变量
zend_hash_update(CG(active_op_array)->static_variables, Z_STR(var_node.u.constant), value);
插入静态变量哈希表后并没有完成,接下来还有一个重要操作:
//生成一条ZEND_FETCH_W的opcode
opline = zend_emit_op(&result, by_ref ? ZEND_FETCH_W : ZEND_FETCH_R, &var_node, NULL);
opline->extended_value = ZEND_FETCH_STATIC;
if (by_ref) {
zend_ast *fetch_ast = zend_ast_create(ZEND_AST_VAR, var_ast);
//生成一条ZEND_ASSIGN_REF的opcode
zend_emit_assign_ref_znode(fetch_ast, &result);
}
后面生成了两条opcode:
通过上面两条opcode可以确定静态变量的读写过程:首先根据变量名在static_variables中取出对应的zval,然后将它修改为引用类型并赋值给局部变量,也就是说static $count = 4;
包含了两个操作,严格的将$count
并不是真正的静态变量,它只是一个指向静态变量的局部变量,执行时实际操作是:$count = & static_variables["count"];
。上面例子$count与static_variables["count"]间的关系如图所示。