深入理解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返回的结果有点区别,不过返回的结果其实是一样的,有兴趣的同学可以去查查):

<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/

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值