变量的作用域是变量的一个作用范围,在这个范围内变量为可见的,即可以访问该变量的代码区域,相反,如果不在这个范围内,变量是不可见的,无法被调用。(全局变量可以将作用范围看作为整个程序)如下面的例子:(会输出什么样的结果呢?)
<?php
$foo = 'tipi';
function variable_scope(){
$foo = 'foo';
[print](http://www.php.net/print) $foo ;
[print](http://www.php.net/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
- 第一章 准备工作和背景知识
- 第一节 环境搭建
- 第二节 源码结构、阅读代码方法
- 第三节 常用代码
- 第四节 小结
- 第二章 用户代码的执行
- 第一节 生命周期和Zend引擎
- 第二节 SAPI概述
- Apache模块
- 嵌入式
- FastCGI
- 第三节 PHP脚本的执行
- 词法分析和语法分析
- opcode
- opcode处理函数查找
- 第四节 小结
- 第三章 变量及数据类型
- 第一节 变量的结构和类型
- 哈希表(HashTable)
- PHP的哈希表实现
- 链表简介
- 第二节 常量
- 第三节 预定义变量
- 第四节 静态变量
- 第五节 类型提示的实现
- 第六节 变量的生命周期
- 变量的赋值和销毁
- 变量的作用域
- global语句
- 第七节 数据类型转换
- 第八节 小结
- 第四章 函数的实现
- 第一节 函数的内部结构
- 函数的内部结构
- 函数间的转换
- 第二节 函数的定义,传参及返回值
- 函数的定义
- 函数的参数
- 函数的返回值
- 第三节 函数的调用和执行
- 第四节 匿名函数及闭包
- 第五节 小结
- 第五章 类和面向对象
- 第一节 类的结构和实现
- 第二节 类的成员变量及方法
- 第三节 访问控制的实现
- 第四节 类的继承,多态及抽象类
- 第五节 魔术方法,延迟绑定及静态成员
- 第六节 PHP保留类及特殊类
- 第七节 对象
- 第八节 命名空间
- 第九节 标准类
- 第十节 小结
- 第六章 内存管理
- 第一节 内存管理概述
- 第二节 PHP中的内存管理
- 第三节 内存使用:申请和销毁
- 第四节 垃圾回收
- 新的垃圾回收
- 第五节 内存管理中的缓存
- 第六节 写时复制(Copy On Write)
- 第七节 内存泄漏
- 第八节 小结
- 第七章 Zend虚拟机
- 第一节 Zend虚拟机概述
- 第二节 语法的实现
- 词法解析
- 语法分析
- 实现自己的语法
- 第三节 中间代码的执行
- 第四节 PHP代码的加密解密
- 第五节 小结
- 第八章 线程安全
- 第二节 线程,进程和并发
- 第三节 PHP中的线程安全
- 第九章 错误和异常处理
- 第十章 输出缓冲
- 第十六章 PHP语言特性的实现
- 第一节 循环语句
- foreach的实现
- 第二十章 怎么样系列(how to)
- 附录
- 附录A PHP及Zend API
- 附录B PHP的历史
- 附录C VLD扩展使用指南
- 附录D 怎样为PHP贡献
- 附录E phpt测试文件说明
- 附录F PHP5.4新功能升级解析
- 附录G:re2c中文手册