通常意義上靜態變數是靜態分配的,他們的生命週期和程式的生命週期一樣, 只有在程式退出時才結束期生命週期,這和區域性變數相反。靜態變數的型別可以分為靜態全域性變數、靜態區域性變、靜態成員變數,最常見的是靜態區域性變數及靜態成員變數,先看看如下區域性變數的使用:
function
t()
t();
t();
t();
上述的程式會輸出1 2 3,從這個示例可以看出,$i變數的值在改變後函式繼續執行還能訪問到,變數i就像是只有函式t()才能訪問到的乙個全域性變數,那php是怎麼實現的呢?這個需要從詞法分析,語法分析,中間**生成到執行中間**這幾個部分**整個實現過程。
1.詞法分析
首先檢視 zend/zend_language_scanner.l檔案,搜尋static關鍵字,我們可以找到如下**(php7.0返回的結果有點區別,不過返回的結果其實是一樣的,有興趣的同學可以去查查):
"static"
| t_static static_var_list ';'
static_var_list:
static_var_list ',' t_variable
| static_var_list ',' t_variable '=' static_scalar
| t_variable
| t_variable '=' static_scalar
;
語法分析的過程中如果匹配到相應的模式則會進行相應的處理動作,該操作一般是進行opcode編譯,但是opcode編譯不屬於語法分析,所以語法分析可以理解為匹配該模式的過程,然後還會進行其它的處理,例如轉換成簡單的表示式。在本
例中的static關鍵字匹配中,是由函式zend_do_fetch_static_variable處理的,即由zend_do_fetch_static_variable進行opcode編譯。
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)
// 將新的靜態變數放進來
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)
fetch_******_variable(&lval, varname, 0 tsrmls_cc); /* relies on the fact
...//省略
}
從上面的**我們可知,在解釋成中間**時,靜態變數是存放cg(active_op_array)->static_variables中,然後opline->opcode的值為zend_fetch_w,opline->op2.u.ea.type為zend_fetch_static,這些是執行中間**的重要資訊。
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)
static
int zend_fastcall zend_fetch_var_address_helper_spec_cv(int type, zend_opcode_handler_args)
break;
empty_switch_default_case()}}
}
static inline hashtable *zend_get_target_symbol_table(const zend_op *opline,
const temp_variable *ts, int type, const zval *variable tsrmls_dc)
return
eg(active_op_array)->static_variables;
break;
}return null;
}
這裡和global關鍵字編譯過程很相似,唯一的區別是,靜態變數中獲取的target_symbol_table,其實是該函式中返回的eg(active_op_array)->static_variables,這是乙個靜態雜湊表,所有對靜態符號表中數值的修改會繼續保留,下次函式執行時繼續從該符號表獲取資訊,也就是說zend為每個函式(準確的說是zend_op_array)分配了乙個私有的符號表來儲存該函式的靜態變數。
參考:
深入理解PHP原理之變數賦值
在前面的文章 深入理解php原理之變數結構 中我已經介紹了php變數的內部結構,下面我將會對變數賦值過程中,php內部對資料處理的原理進行闡述,不過在講述該原理前,需要先了解一下變數名和它的值是如何關聯起來的,這個對變數賦值內部原理的理解非常重要,例如 a 1 這個例子看起來非常簡單,但是你知道 變...
深入理解PHP原理之變數宣告
在php中沒有對常規變數的宣告操作,如果要使用乙個變數,直接進行賦值操作即可,因為php在賦值操作的同時已經進行宣告操作,那麼php是怎樣在賦值前進行宣告的呢?在博文 深入理解php原理之變數賦值 中其實已經提到過變數的宣告,但是講述的不夠透徹,下面主要通過詞法分析 語法分析和獲取左值和右值的過程,...
深入理解PHP原理之變數作用域
php變數的內部表示是如何和使用者指令碼中的變數聯絡起來的呢?也就是說,如果我在指令碼中寫下 var laruence echo var ze是如何把我的變數var和內部結構zval聯絡起來的呢?深入理解php原理之變數中講過,php內部都是使用zval來表示變數的,但是對於上面的指令碼,我們的變數...