php源码之路第三章第六节( 变量的作用域和global语句)
来源:互联网 发布:网络尖兵破解软件 编辑:程序博客网 时间:2024/06/07 00:55
变量的作用域是变量的一个作用范围,在这个范围内变量为可见的,即可以访问该变量的代码区域,相反,如果不在这个范围内,变量是不可见的,无法被调用。全局变量可以将作用范围看作为整个程序。代码如下:
<?php $foo = 'tipi'; function variable_scope(){ $foo = 'foo'; print $foo ; print $bar ; }
由此可见,变量的作用域是一个很基础的概念,在变量的实现中比较重要。全局变量与局部变量 变量按作用域类型分为:全局变量和局部变量。全局变量是在整个程序中任何地方随意调用的变量,在PHP中,全局变量的“全局化”使用gloal语句来实现。相对于全局变量,局部变量的作用域是程序中的部分代码(如函数中),而不是程序的全部。 变量的作用域与变量的生命周期有一定的联系,如在一个函数中定义的变量,这个变量的作用域从变量声明的时候开始到这个函数结束的时候。这种变量我们称之为局部变量。它的生命周期开始于函数开始,结束于函数的调用完成之时。 变量的作用域决定其生命周期吗?程序运行到变量作用域范围之外,就会将变量进行销毁吗? 对于不同作用域的变量,如果存在冲突情况,就像上面的例子中,全局变量中有一个名为$bar的变量,在局部变量中也存在一个名为$bar的变量,此时如何区分呢? 对于全局变量,Zend引擎有一个_zend_executor_globals结构,该结构中的symbol_table就是全局符号表,其中保存了在顶层作用域中的变量。 同样,函数或者对象的方法在被调用时会创建active_symbol_table来保存局部变量。当程序在顶层中使用某个变量时,ZE就会在symbol_table中进行遍历,同理,如果程序运行于某个函数中,Zend引擎会遍历查询与其对应的active_symbol_table,而每个函数的active_symbol_table是相对独立的,由此而实现的作用域的独立。展开来看,如果我们调用的一个函数中的变量,ZE使用_zend_execute_data来存储某个单独的op_array(每个函数都会生成单独的op_array)执行过程中所需要的信息,它的结构如下:
struct _zend_execute_data { struct _zend_op *opline; zend_function_state function_state; zend_function *fbc; /* Function Being Called */ zend_class_entry *called_scope; zend_op_array *op_array; zval *object; union _temp_variable *Ts; zval ***CVs; HashTable *symbol_table; struct _zend_execute_data *prev_execute_data; zval *old_error_reporting; zend_bool nested; zval **original_return_value; zend_class_entry *current_scope; zend_class_entry *current_called_scope; zval *current_this; zval *current_object; struct _zend_op *call_opline;};
函数中的局部变量就存储在_zend_execute_data的symbol_table中,在执行当前函数的op_array时,全局zend_executor_globals中的*active_symbol_table会指向当前_zend_execute_data中的*symbol_table。 因为每个函数调用开始时都会重新初始化EG(active_symbol_table)为NULL,在这个函数的所有opcode的执行过程中这个全局变量会一直存在,并且所有的局部变量修改都是在它上面操作完成的,如前面的赋值操作等。而此时,其他函数中的symbol_table会存放在栈中,将当前函数执行完并返回时,程序会将之前保存的zend_execute_data恢复,从而其他函数中的变量也就不会被找到,局部变量的作用域就是以这种方式来实现的。 相关操作在 Zend/zend_vm_execute.h 文件中定义的execute函数中一目了然,如下所示代码:
zend_vm_enter:/* Initialize execute_data */execute_data = (zend_execute_data *)zend_vm_stack_alloc( sizeof(zend_execute_data) + sizeof(zval**) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2) + sizeof(temp_variable) * op_array->T TSRMLS_CC);EX(symbol_table) = EG(active_symbol_table);EX(prev_execute_data) = EG(current_execute_data);EG(current_execute_data) = execute_data;
所以,变量的作用域是使用不同的符号表来实现的,于是顶层的全局变量在函数内部使用时,需要先使用global语句来将变量“挪”到函数独立的*active_symbol_table中,即变量的跨域操作。(关于global的详细解释,见下一小节) 在PHP的源码中,EX宏经常出现,它的作用是获取结构体zend_execute_data的字段值,它的实现是: #define EX(element) execute_data->element
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;}
在前面语法分析过程中,程序传递的参数是 ZEND_FETCH_GLOBAL_LOCK,于是如上所示。我们取&EG(symbol_table);的值。这也是全局变量的存放位置。如上就是整个global的解析过程。
0 0
- php源码之路第三章第六节( 变量的作用域和global语句)
- php源码之路第三章第六节( 变量的生命周期之变量的赋值和销毁)
- php中Global变量和$GLOBALS [ ]的作用
- python global语句 变量作用域
- php 函数变量的作用域 两个关键词global 和static
- [李景山php] 深入理解PHP内核[读书笔记]--第三章:变量及数据类型--global语句
- php变量作用域(花括号、global、闭包)
- PHP变量作用域(花括号、global、闭包)
- php源码之路第三章第四节( 静态变量)
- [李景山php] 深入理解PHP内核[读书笔记]--第三章:变量及数据类型--变量的作用域
- php源码之路第三章第三节( 预定义变量)
- PHP开始啦--变量作用域,global关键词,static关键词
- php 全局变量global的作用域
- php源码之路第三章第二节( 常量)
- php源码之路第六章第三节 (内存使用:申请和销毁)
- php源码之路第五章第六节 (PHP保留类及特殊类)
- php源码之路第六章第二节 (PHP中的内存管理)
- php变量的定义和作用域
- 百度前端学院2015task2自学总结
- NOJ 1001 二分查找
- 2014ACM/ICPC亚洲区鞍山赛区现场赛 题解
- Servlet的生命周期
- 除法求逆元(扩展欧几里德和费马小定理)
- php源码之路第三章第六节( 变量的作用域和global语句)
- centos 7.2 防火墙配置
- 用string类构造器创建对象
- [已解决]mac android studio安装报错java.lang.RuntimeException: java.lang.IllegalArgumentException: Argument
- 数据结构看书笔记(九)--图的存储结构及遍历
- shell脚本 for循环迭代文件
- 最大子段和
- 理解java异常处理机制
- 洛谷P1005&NOIP2007 矩阵取数游戏