變量的作用域是變量的一個作用范圍,在這個范圍內變量為可見的,即可以訪問該變量的代碼區域,相反,如果不在這個范圍內,變量是不可見的,無法被調用。(全局變量可以將作用范圍看作為整個程序)如下面的例子:(會輸出什么樣的結果呢?)
<?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中文手冊