天天看點

四十一、PHP核心探索:全局變量Global ☞ Global語句解析過程

global語句的作用是定義全局變量,例如如果想在函數内通路全局作用域内的變量則可以通過global聲明來定義。 下面從文法解釋開始分析。

1. 詞法解析

檢視 Zend/zend_language_scanner.l檔案,搜尋 global關鍵字。我們可以找到如下代碼:

<ST_IN_SCRIPTING>"global" {

return T_GLOBAL;

}

2. 文法解析

在詞法解析完後,獲得了token,此時通過這個token,我們去Zend/zend_language_parser.y檔案中查找。找到相關代碼如下:

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

;

上面代碼中的$3是指global_var(如果不清楚yacc的文法,可以查閱yacc入門類的文章。)

從上面的代碼可以知道,對于全局變量的聲明調用的是zend_do_fetch_global_variable函數,查找此函數的實作在Zend/zend_compile.c檔案。

void zend_do_fetch_global_variable(znode *varname, const znode *static_assignment, int fetch_type TSRMLS_DC) 
{
        ...//省略
        opline->opcode = ZEND_FETCH_W;      /* the default mode must be Write, since fetch_simple_variable() is used to define function arguments */
        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 = fetch_type;
        result = opline->result;
 
        ... // 省略
        fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact that the default fetch is BP_VAR_W */
 
        zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC);
        CG(active_op_array)->opcodes[CG(active_op_array)->last-1].result.u.EA.type |= EXT_TYPE_UNUSED;
}
/* }}} */      

上面的代碼确認了opcode為ZEND_FETCH_W外,還執行了zend_do_assign_ref函數。zend_do_assign_ref函數的實作如下:

void zend_do_assign_ref(znode *result, const znode *lvar, const znode *rvar TSRMLS_DC) /* {{{ */

{

zend_op *opline;

... //省略

opline = get_next_op(CG(active_op_array) TSRMLS_CC);

opline->opcode = ZEND_ASSIGN_REF;

...//省略

if (result) {

opline->result.op_type = IS_VAR;

opline->result.u.EA.type = 0;

opline->result.u.var = get_temporary_variable(CG(active_op_array));

*result = opline->result;

} else {

/* SET_UNUSED(opline->result); */

opline->result.u.EA.type |= EXT_TYPE_UNUSED;

}

opline->op1 = *lvar;

opline->op2 = *rvar;

}

從上面的zend_do_fetch_global_variable函數和zend_do_assign_ref函數的實作可以看出, 使用global聲明一個全局變量後,其執行了兩步操作,ZEND_FETCH_W和ZEND_ASSIGN_REF。

3. 生成并執行中間代碼

我們看下ZEND_FETCH_W的最後執行。從代碼中我們可以知道:

  • ZEND_FETCH_W = 83
  • op->op1.op_type = 4
  • op->op2.op_type = 0

而計算最後調用的方法在代碼中的展現為:

zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1.op_type] * 5 + zend_vm_decode[op->op2.op_type]];

計算,最後調用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);

}

在zend_fetch_var_address_helper_SPEC_CV中調用如下代碼擷取符号表

target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), type, varname TSRMLS_CC);

在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_GLOBAL:

case ZEND_FETCH_GLOBAL_LOCK:

return &EG(symbol_table);

break;

... // 省略

}

return NULL;

}