Press "Enter" to skip to content

PHP源码分析之Global关键字

闲来无事,就系统的从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中分配相应的变量。
通过这样的机制,实现了全局变量。

14 Comments

  1. 郭新华
    郭新华 January 20, 2015

    好多错别字啊 鸟哥
    zend_fetch_var_adress_helper
    zend_get_targer_symbol_table

  2. HornedReaper
    HornedReaper June 26, 2014

    看了你后面一篇文章,我发现我错了…

  3. HornedReaper
    HornedReaper June 26, 2014

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

  4. HornedReaper
    HornedReaper June 26, 2014

    hello();
    看这个问题一直没人回答我就顺手贴上去吧,class的顺序要换一下,php class继承的时候是顺序执行的,继承的父类如果没有定义当然就找不到了

  5. 李晓星
    李晓星 April 2, 2014

    很受用,从 zend_get_target_symbol_table 这里也可以看到静态变量的分配规则了,谢谢。

  6. xinbe
    xinbe October 3, 2010

    那請問用$GLOBALS[‘var’]
    跟global $var
    哪個會比較快呢?

  7. 雪候鸟
    雪候鸟 August 25, 2008

    恩,因为刚才我发现我的思路可能有点问题,所以又重新验证了一下,这个过程中怕误导别人,就先给隐藏了。 现在好了。

  8. blankyao
    blankyao August 25, 2008

    那篇讲多重继承的bug的文章呢?只在阅读器上看到了….

  9. 雪候鸟
    雪候鸟 August 24, 2008

    这个问题确实奇怪, 一层继承的时候就没有问题。估计是PHP5的一个bug。
    回头我研究研究。

  10. guoxiaod
    guoxiaod August 24, 2008

    再发一边。
    <?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();

  11. guoxiaod
    guoxiaod August 24, 2008

    哎,居然,把 <? 过滤掉了。

  12. guoxiaod
    guoxiaod August 24, 2008

    之前我们遇到了一个问题:
    关于PHP class 的顺序的问题,问题如下:
    hello();
    提示找不到 class b .
    这个问题,是什么原因呢?

Comments are closed.