通常意義上靜態變量是靜態分配的,他們的生命周期和程序的生命周期一樣,只有在程序退出時才結束期生命周期,這和局部變量相反,有的語言中全局變量也是靜態分配的。例如PHP和Javascript中的全局變量。
靜態變量可以分為:
- 靜態全局變量,PHP中的全局變量也可以理解為靜態全局變量,因為除非明確unset釋放,在程序運行過程中始終存在。
- 靜態局部變量,也就是在函數內定義的靜態變量,函數在執行時對變量的操作會保持到下一次函數被調用。
- 靜態成員變量,這是在類中定義的靜態變量,和實例變量相對應,靜態成員變量可以在所有實例中共享。
最常見的是靜態局部變量及靜態成員變量。局部變量只有在函數執行時才會存在。通常,當一個函數執行完畢,它的局部變量的值就已經不存在,而且變量所占據的內存也被釋放。當下一次執行該過程時,它的所有局部變量將重新初始化。如果某個局部變量定義為靜態的,則它的值不會在函數調用結束后釋放,而是繼續保留變量的值。
在本小節將介紹靜態局部變量,有關靜態成員變量的內容將在類與對象章節進行介紹。
先看看如下局部變量的使用:
function t() {
static $i = 0;
$i++;
echo $i, ' ';
}
?
t();
t();
t();
上面的程序會輸出1 2 3。從這個示例可以看出,$i變量的值在改變后函數繼續執行還能訪問到,$i變量就像是只有函數t()才能訪問到的一個全局變量。那PHP是怎么實現的呢?
static是PHP的關鍵字,我們需要從詞法分析,語法分析,中間代碼生成到執行中間代碼這幾個部分探討整個實現過程。
## 1. 詞法分析
首先查看 Zend/zend_language_scanner.l文件,搜索 static關鍵字。我們可以找到如下代碼:
<ST_IN_SCRIPTING>"static" {
return T_STATIC;
}
## 2. 語法分析
在詞法分析找到token后,通過這個token,在Zend/zend_language_parser.y文件中查找。找到相關代碼如下:
| T_STATIC static_var_list ';'
?
static_var_list:
static_var_list ',' T_VARIABLE { zend_do_fetch_static_variable(&$3, NULL, ZEND_FETCH_STATIC TSRMLS_CC); }
| static_var_list ',' T_VARIABLE '=' static_scalar { zend_do_fetch_static_variable(&$3, &$5, ZEND_FETCH_STATIC TSRMLS_CC); }
| T_VARIABLE { zend_do_fetch_static_variable(&$1, NULL, ZEND_FETCH_STATIC TSRMLS_CC); }
| T_VARIABLE '=' static_scalar { zend_do_fetch_static_variable(&$1, &$3, ZEND_FETCH_STATIC TSRMLS_CC); }
?
;
語法分析的過程中如果匹配到相應的模式則會進行相應的處理動作,通常是進行opcode的編譯。在本例中的static關鍵字匹配中,是由函數zend_do_fetch_static_variable處理的。
## 3. 生成opcode中間代碼
zend_do_fetch_static_variable函數的作用就是生成opcode,定義如下:
void zend_do_fetch_static_variable(znode *varname, const znode
*static_assignment, int fetch_type TSRMLS_DC)
{
zval *tmp;
zend_op *opline;
znode lval;
znode result;
?
ALLOC_ZVAL(tmp);
?
if (static_assignment) {
*tmp = static_assignment->u.constant;
} else {
INIT_ZVAL(*tmp);
}
if (!CG(active_op_array)->static_variables) { /* 初始化此時的靜態變量存放位置 */
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 2, NULL, ZVAL_PTR_DTOR, 0);
}
// 將新的靜態變量放進來
zend_hash_update(CG(active_op_array)->static_variables, varname->u.constant.value.str.val,
varname->u.constant.value.str.len+1, &tmp, sizeof(zval *), NULL);
?
...//省略
opline = get_next_op(CG(active_op_array) TSRMLS_CC);
opline->opcode = (fetch_type == ZEND_FETCH_LEXICAL) ? ZEND_FETCH_R : ZEND_FETCH_W; /* 由于fetch_type=ZEND_FETCH_STATIC,程序會選擇ZEND_FETCH_W*/
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 = ZEND_FETCH_STATIC; /* 這在中間代碼執行時會有大用 */
result = opline->result;
?
if (varname->op_type == IS_CONST) {
zval_copy_ctor(&varname->u.constant);
}
fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact that the default fetch is BP_VAR_W */
?
if (fetch_type == ZEND_FETCH_LEXICAL) {
...//省略
} else {
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;
?
}
從上面的代碼我們可知,在解釋成中間代碼時,靜態變量是存放在CG(active_op_array)->static_variables中的。并且生成的中間代碼為:**ZEND_FETCH_W** 和 **ZEND_ASSIGN_REF** 。其中ZEND_FETCH_W中間代碼是在zend_do_fetch_static_variable中直接賦值,而ZEND_ASSIGN_REF中間代碼是在zend_do_fetch_static_variable中調用zend_do_assign_ref生成的。
## 4. 執行中間代碼
opcode的編譯階段完成后就開始opcode的執行了。在Zend/zend_vm_opcodes.h文件中包含所有opcode的宏定義,這些宏并沒有特殊含義,只是作為opcode的唯一標示,包含本例中相關的如下兩個宏的定義:
#define ZEND_FETCH_W 83
#define ZEND_ASSIGN_REF 39
前面第二章 [腳本的執行一節](#)介紹了根據opcode查找到相應處理函數的方法。通過中間代碼調用映射方法計算得此時ZEND_FETCH_W 對應的操作為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);
}
?
static int ZEND_FASTCALL zend_fetch_var_address_helper_SPEC_CV(int type, ZEND_OPCODE_HANDLER_ARGS)
{
...//省略
?
if (opline->op2.u.EA.type == ZEND_FETCH_STATIC_MEMBER) {
retval = zend_std_get_static_property(EX_T(opline->op2.u.var).class_entry, Z_STRVAL_P(varname), Z_STRLEN_P(varname), 0 TSRMLS_CC);
} else {
// 取符號表,這里我們取的是EG(active_op_array)->static_variables
target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), type, varname TSRMLS_CC);
...// 省略
if (zend_hash_find(target_symbol_table, varname->value.str.val, varname->value.str.len+1, (void **) &retval) == FAILURE) {
switch (type) {
...//省略
// 在前面的調用中我們知道type = case BP_VAR_W,于是程序會走按case BP_VAR_W的流程走。
case BP_VAR_W: {
zval *new_zval = &EG(uninitialized_zval);
?
Z_ADDREF_P(new_zval);
zend_hash_update(target_symbol_table, varname->value.str.val, varname->value.str.len+1, &new_zval, sizeof(zval *), (void **) &retval);
// 更新符號表,執行賦值操作
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
}
switch (opline->op2.u.EA.type) {
...//省略
case ZEND_FETCH_STATIC:
zval_update_constant(retval, (void*) 1 TSRMLS_CC);
break;
case ZEND_FETCH_GLOBAL_LOCK:
if (IS_CV == IS_VAR && !free_op1.var) {
PZVAL_LOCK(*EX_T(opline->op1.u.var).var.ptr_ptr);
}
break;
}
}
?
...//省略
}
在上面的代碼中有一個關鍵的函數zend_get_target_symbol_table。它的作用是獲取當前正在執行的目標符號表,而在函數執行時當前的op_array則是函數體本身,先看看zend_op_array的結構。
struct _zend_op_array {
/* Common elements */
zend_uchar type;
char *function_name;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
?
zend_bool done_pass_two;
?
zend_uint *refcount;
?
zend_op *opcodes;
zend_uint last, size;
?
/* static variables support */
HashTable *static_variables;
?
zend_op *start_op;
int backpatch_count;
?
zend_uint this_var;
// ...
}
由上可以看到zend_op_array中包含function_name字段,也就是當前函數的名稱。再看看獲取當前符號表的函數:
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_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;
}
在前面的zend_do_fetch_static_variable執行時,op2.u.EA.type的值為ZEND_FETCH_STATIC,從而這zend_get_target_symbol_table函數中我們返回的是EG(active_op_array)->static_variables。也就是當前函數的的靜態變量哈希表。每次執行時都會從該符號表中查找相應的值,由于op_array在程序執行時始終存在。所有對靜態符號表中數值的修改會繼續保留,下次函數執行時繼續從該符號表獲取信息。也就是說Zend為每個函數(準確的說是zend_op_array)分配了一個私有的符號表來保存該函數的靜態變量。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和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中文手冊