## 2.3 靜態變量
PHP中局部變量分配在zend_execute_data結構上,每次執行zend_op_array都會生成一個新的zend_execute_data,局部變量在執行之初分配,然后在執行結束時釋放,這是局部變量的生命周期,而局部變量中有一種特殊的類型:靜態變量,它們不會在函數執行完后釋放,當程序執行離開函數域時靜態變量的值被保留下來,下次執行時仍然可以使用之前的值。
PHP中的靜態變量通過`static`關鍵詞創建:
```php
function my_func(){
static $count = 4;
$count++;
echo $count,"\n";
}
my_func();
my_func();
===========================
5
6
```
### 2.3.1 靜態變量的存儲
靜態變量既然不會隨執行的結束而釋放,那么很容易想到它的保存位置:`zend_op_array->static_variables`,這是一個哈希表,所以PHP中的靜態變量與普通局部變量不同,它們沒有分配在執行空間zend_execute_data上,而是以哈希表的形式保存在zend_op_array中。
> 靜態變量只會初始化一次,注意:它的初始化發生在編譯階段而不是執行階段,上面這個例子中:`static $count = 4;`是在編譯階段發現定義了一個靜態變量,然后插進了zend_op_array->static_variables中,并不是執行的時候把static_variables中的值修改為4,所以上面執行的時候會輸出5、6,再次執行并沒有重置靜態變量的值。
>
> 這個特性也意味著靜態變量初始的值不能是變量,比如:`static $count = $xxx;`這樣定義將會報錯。
### 2.3.2 靜態變量的訪問
局部變量通過編譯時確定的編號進行讀寫操作,而靜態變量通過哈希表保存,這就使得其不能像普通變量那樣有一個固定的編號,有一種可能是通過變量名索引的,那么究竟是否如此呢?我們分析下其編譯過程。
靜態變量編譯的語法規則:
```c
statement:
...
| T_STATIC static_var_list ';' { $$ = $2; }
...
;
static_var_list:
static_var_list ',' static_var { $$ = zend_ast_list_add($1, $3); }
| static_var { $$ = zend_ast_create_list(1, ZEND_AST_STMT_LIST, $1); }
;
static_var:
T_VARIABLE { $$ = zend_ast_create(ZEND_AST_STATIC, $1, NULL); }
| T_VARIABLE '=' expr { $$ = zend_ast_create(ZEND_AST_STATIC, $1, $3); }
;
```
語法解析后生成了一個`ZEND_AST_STATIC`語法樹節點,接著再看下這個節點編譯為opcode的過程:zend_compile_static_var。
```c
void zend_compile_static_var(zend_ast *ast)
{
zend_ast *var_ast = ast->child[0];
zend_ast *value_ast = ast->child[1];
zval value_zv;
if (value_ast) {
//定義了初始值
zend_const_expr_to_zval(&value_zv, value_ast);
} else {
//無初始值
ZVAL_NULL(&value_zv);
}
zend_compile_static_var_common(var_ast, &value_zv, 1);
}
```
這里首先對初始化值進行編譯,最終得到一個固定值,然后調用:`zend_compile_static_var_common()`處理,首先判斷當前編譯的`zend_op_array->static_variables`是否已創建,未創建則分配一個HashTable,接著將定義的靜態變量插入:
```c
//zend_compile_static_var_common():
if (!CG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 8, NULL, ZVAL_PTR_DTOR, 0);
}
//插入靜態變量
zend_hash_update(CG(active_op_array)->static_variables, Z_STR(var_node.u.constant), value);
```
插入靜態變量哈希表后并沒有完成,接下來還有一個重要操作:
```c
//生成一條ZEND_FETCH_W的opcode
opline = zend_emit_op(&result, by_ref ? ZEND_FETCH_W : ZEND_FETCH_R, &var_node, NULL);
opline->extended_value = ZEND_FETCH_STATIC;
if (by_ref) {
zend_ast *fetch_ast = zend_ast_create(ZEND_AST_VAR, var_ast);
//生成一條ZEND_ASSIGN_REF的opcode
zend_emit_assign_ref_znode(fetch_ast, &result);
}
```
后面生成了兩條opcode:
* __ZEND_FETCH_W:__ 這條opcode對應的操作是創建一個IS_INDIRECT類型的zval,指向static_variables中對應靜態變量的zval
* __ZEND_ASSIGN_REF:__ 它的操作是引用賦值,即將一個引用賦值給CV變量
通過上面兩條opcode可以確定靜態變量的讀寫過程:首先根據變量名在static_variables中取出對應的zval,然后將它修改為引用類型并賦值給局部變量,也就是說`static $count = 4;`包含了兩個操作,嚴格的將`$count`并不是真正的靜態變量,它只是一個指向靜態變量的局部變量,執行時實際操作是:`$count = & static_variables["count"];`。上面例子$count與static_variables["count"]間的關系如圖所示。

- 前言
- 第1章 PHP基本架構
- 1.1 PHP簡介
- 1.2 PHP7的改進
- 1.3 FPM
- 1.3.1 概述
- 1.3.2 基本實現
- 1.3.3 FPM的初始化
- 1.3.4 請求處理
- 1.3.5 進程管理
- 1.4 PHP執行的幾個階段
- 第2章 變量
- 2.1 變量的內部實現
- 2.2 數組
- 2.3 靜態變量
- 2.4 全局變量
- 2.5 常量
- 第3章 Zend虛擬機
- 3.1 PHP代碼的編譯
- 3.1.1 詞法解析、語法解析
- 3.1.2 抽象語法樹編譯流程
- 3.2 函數實現
- 3.2.1 內部函數
- 3.2.2 用戶函數的實現
- 3.3 Zend引擎執行流程
- 3.3.1 基本結構
- 3.3.2 執行流程
- 3.3.3 函數的執行流程
- 3.3.4 全局execute_data和opline
- 3.4 面向對象實現
- 3.4.1 類
- 3.4.2 對象
- 3.4.3 繼承
- 3.4.4 動態屬性
- 3.4.5 魔術方法
- 3.4.6 類的自動加載
- 3.5 運行時緩存
- 3.6 Opcache
- 3.6.1 opcode緩存
- 3.6.2 opcode優化
- 3.6.3 JIT
- 第4章 PHP基礎語法實現
- 4.1 類型轉換
- 4.2 選擇結構
- 4.3 循環結構
- 4.4 中斷及跳轉
- 4.5 include/require
- 4.6 異常處理
- 第5章 內存管理
- 5.1 Zend內存池
- 5.2 垃圾回收
- 第6章 線程安全
- 6.1 什么是線程安全
- 6.2 線程安全資源管理器
- 第7章 擴展開發
- 7.1 概述
- 7.2 擴展的實現原理
- 7.3 擴展的構成及編譯
- 7.3.1 擴展的構成
- 7.3.2 編譯工具
- 7.3.3 編寫擴展的基本步驟
- 7.3.4 config.m4
- 7.4 鉤子函數
- 7.5 運行時配置
- 7.5.1 全局變量
- 7.5.2 ini配置
- 7.6 函數
- 7.6.1 內部函數注冊
- 7.6.2 函數參數解析
- 7.6.3 引用傳參
- 7.6.4 函數返回值
- 7.6.5 函數調用
- 7.7 zval的操作
- 7.7.1 新生成各類型zval
- 7.7.2 獲取zval的值及類型
- 7.7.3 類型轉換
- 7.7.4 引用計數
- 7.7.5 字符串操作
- 7.7.6 數組操作
- 7.8 常量
- 7.9 面向對象
- 7.9.1 內部類注冊
- 7.9.2 定義成員屬性
- 7.9.3 定義成員方法
- 7.9.4 定義常量
- 7.9.5 類的實例化
- 7.10 資源類型
- 7.11 經典擴展解析
- 7.8.1 Yaf
- 7.8.2 Redis
- 第8章 命名空間
- 8.1 概述
- 8.2 命名空間的定義
- 8.2.1 定義語法
- 8.2.2 內部實現
- 8.3 命名空間的使用
- 8.3.1 基本用法
- 8.3.2 use導入
- 8.3.3 動態用法
- 附錄
- break/continue按標簽中斷語法實現
- defer推遲函數調用語法的實現
- 一起線上事故引發的對PHP超時控制的思考