- 本文地址: https://www.laruence.com/2008/08/24/377.html
- 转载请注明出处
闲来无事,就系统的从PHP的词法分析,语法分析,opcodes生成,执行,整个流程,详细的分析了global关键字的实现。
当你在脚本中写下:
<?php $var = "laruence"; function sample(){ global $var; } ?>
的时候,你知道PHP是怎么实现在函数作用域找到全局变量的么?
在我前面的文章中(深入理解PHP原理之Opcodes)讲过, PHP的执行,经历了如下几个阶段:
1. Scanning(Lexing) ,将PHP代码转换为语言片段(Tokens) 2. Parsing, 将Tokens转换成简单而有意义的表达式 3. Compilation, 将表达式编译成Opocdes 4. Execution, 顺次执行Opcodes,每次一条,从而实现PHP脚本的功能。
那么,第一阶段,自然就是Scanning了, 在词法分析阶段,我们的
global $var;
会被解析为:
T_GLOBAL var;
接下来是parsing阶段:
T_GLOBAL var;
yacc经过规则:
statement: | T_GLOBAL global_var_list ';' .... global_var_list: global_var_list ',' global_var { zend_do_fetch_global_variable(&$3, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); } | global_var { zend_do_fetch_global_variable(&$1, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); } ;
其中, zend_do_fetch_global_variable是真正生成opcode的函数:
zend_op *opline; ...... opline->opcode = ZEND_FETCH_W; opline->result.op_type = IS_VAR; ...... opline->op2.u.EA.type = ZEND_FETCH_GLOBAL_LOCK;
而对于ZEND_FETCH_W的op_handler是:
ZEND_VM_HANDLER(83, ZEND_FETCH_W, CONST|TMP|VAR|CV, ANY) { ZEND_VM_DISPATCH_TO_HELPER_EX(zend_fetch_var_address_helper, type, BP_VAR_W); }
我们来看看zend_fetch_var_adress_helper:
..... target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), type, varname TSRMLS_CC); /* if (!target_symbol_table) { ZEND_VM_NEXT_OPCODE(); } */ if (zend_hash_find(target_symbol_table, varname->value.str.val, varname->value.str.len+1, (void **) &retval) == FAILUR E) { switch (type) { case BP_VAR_R: case BP_VAR_UNSET: zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname)); /* break missing intentionally */ case BP_VAR_IS: retval = &EG(uninitialized_zval_ptr); break; case BP_VAR_RW: zend_error(E_NOTICE,"Undefined variable: %s", Z_STRVAL_P(varname)); /* break missing intentionally */ case BP_VAR_W: { zval *new_zval = &EG(uninitialized_zval); new_zval->refcount++; zend_hash_update(target_symbol_table, varname->value.str.val, varname->value.str.len+1, &new_zval, siz eof(zval *), (void **) &retval); } break; EMPTY_SWITCH_DEFAULT_CASE() } }
可以看出,核心就是zend_get_targer_symbol_table这个函数了:
static inline HashTable *zend_get_target_symbol_table(zend_op *opline, temp_variable *Ts, int type, zval *variable TSRMLS_DC) { switch (opline->op2.u.EA.type) { case ZEND_FETCH_LOCAL: return EG(active_symbol_table); break; case ZEND_FETCH_GLOBAL: case ZEND_FETCH_GLOBAL_LOCK: return &EG(symbol_table); break; 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; }
恩,问题清楚了,也就是说, 如果你global了一个变量,那么Zend就会去全局symbol_table去寻找,如果找不到,就会在全局symbol_table中分配相应的变量。
通过这样的机制,实现了全局变量。
好多错别字啊 鸟哥
zend_fetch_var_adress_helper
zend_get_targer_symbol_table
[…] PHP源码分析之Global关键字 […]
看了你后面一篇文章,我发现我错了…
class c {
public function hello (){echo “ok3”;}
};
class b extends c {
public function hello(){echo “ok2”;}
};
class a extends b {
public function hello(){echo “ok1”;}
};
$a = new a();
$a->hello();
hello();
看这个问题一直没人回答我就顺手贴上去吧,class的顺序要换一下,php class继承的时候是顺序执行的,继承的父类如果没有定义当然就找不到了
很受用,从 zend_get_target_symbol_table 这里也可以看到静态变量的分配规则了,谢谢。
[…] 本文地址: http://www.laruence.com/2008/08/24/377.html […]
那請問用$GLOBALS[‘var’]
跟global $var
哪個會比較快呢?
恩,因为刚才我发现我的思路可能有点问题,所以又重新验证了一下,这个过程中怕误导别人,就先给隐藏了。 现在好了。
那篇讲多重继承的bug的文章呢?只在阅读器上看到了….
这个问题确实奇怪, 一层继承的时候就没有问题。估计是PHP5的一个bug。
回头我研究研究。
再发一边。
<?php
class a extends b {
public function hello(){echo “ok1”;}
};
class b extends c {
public function hello(){echo “ok2”;}
};
class c {
public function hello (){echo “ok3”;}
};
$a = new a();
$a->hello();
哎,居然,把 <? 过滤掉了。
之前我们遇到了一个问题:
关于PHP class 的顺序的问题,问题如下:
hello();
提示找不到 class b .
这个问题,是什么原因呢?