1、PHP變量的底層實現
PHP代碼執行圖解
1.1 變量在内存中的存儲結構
PHP變量是通過zval結構體來存儲的,文档: Zend/zend.h 316行左右
1.2 值的存儲
PHP變量的值是放在zval結構體中的value段中的,文档: Zend/zend.h
1.3 結構體的字段解釋
struct _zval_struct { /* Variable information */ zvalue_value value;/*變量的值,是個聯合體*/ zend_uint refcount__gc; /*指向次數*/ zend_uchar type;/* 變量類型 */ zend_uchar is_ref__gc; /*是否引用*/ }; //type字段的值爲以下常量 IS_NULL, IS_BOOL,IS_LONG,IS_DOUBLE IS_STRING,IS_ARRAY,IS_OBJECT IS_RESOURCE
1.4 聯合體中的值
typedef union _zvalue_value { long lval;/* long value */ double dval;/* double value */ struct { char *val; int len; } str; HashTable *ht;/* hash table value */ zend_object_value obj; } zvalue_value;
聯合中爲什麽只列出了5種值?
NULL不用,zval的type爲IS_NULL即可
Bool以1,0存儲在lval上
resource的type爲resource,其resource的内容用long來標志(資源標記)
1.5 變量的結構圖
1.6 變量的創建
創建變量的步驟: $str = "hello";
1:創建zval結構,並設置其類型 IS_STRING
2:設置其值爲 hello
3:講其加入符號表
{ zval *fooval; MAKE_STD_ZVAL(fooval); ZVAL_STRING(fooval, "hello", 1); ZEND_SET_SYMBOL( EG(active_symbol_table) , "foo" , fooval); }
1.7 符號表 symbol_table
符號表是什麽?
符號表是一張哈希表,裡面存儲了變量名->變量的zval結構體的地址,
// zend/zend_globals.h 182行 struct _zend_executor_globals { ... ... HashTable *active_symbol_table; /*活動符號表*/ HashTable symbol_table;/* 全局符號表 */ HashTable included_files;/* files already included */
1.8 符號表與函數
Zend/zend_compiles.h struct _zend_execute_data { ... zend_op_array *op_array; //函數的執行步驟 HashTable *symbol_table; // 此函數的符號表地址 zend_class_entry *current_scope; zval *current_this; zval *current_object; ... };
上面這個,是當前函數執行時的符號表
1.9 符號表與作用域
當執行到函數時,會生成函數的"執行環境結構體",包含函數名,參數,執行步驟,所在的類(如果是方法),以及爲這個函數生成一個符號表.符號表統一放在棧上.並把active_symbol_table指向剛産生的符號表
1.10 函數中靜態變量的實現
2.0 常量-常量結構體
結構體 Zend/constants.h 33行
typedef struct _zend_constant { zval value; //變量結構體 int flags; //標志,是否大小寫敏感等 char *name; //常量名 uint name_len; int module_number;//模塊名 } zend_constant;
2.1 常量的生成
int zend_register_constant(zend_constant *c TSRMLS_DC) { ... ... zend_hash_add(EG(zend_constants), name, c->name_len, (void *) c, sizeof(zend_constant), NULL)==FAILURE) ... ... }
2.2 define函數的實現
define函數當然是調用zend_register_constant聲明的常量 :)
具體如下 Zend/zend_builtin_functions.c
關鍵代碼:
c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = zend_strndup(name, name_len); c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; }
2.3 有趣的測試
1:常量並沒有檢測名字...
define('^_^',"laugh");
2:常量的第2個參數還可以是對象,與手冊上介紹的不同
define('obj',new className());
(當然,對象要有toString方法等著)
2.4 常量爲什麽是全局有效的?
很簡單,常量的哈希表只有一個
EG(zend_constants)
3.0 内存管理與垃圾回收
PHP封裝了對系統内存的請求,不要直接用malloc直接請求内存
3.1 PHP的hashtable太強大
3.2 引用計數
<?php $a = 1; $b = $a;
$a,$b的值及類型 都一樣,有必要再申請一個zval結構嗎?
3.3 引用計數解釋
<?php $a = 1; $b = $a;
當$a的值賦給$b時,並沒有爲$b生成一個新的zval結構體.而是$b與$a共享一個結構體.
3.4:copy-on-write 寫時複制
<?php $a = 1; $b = $a; $b=6
3.5 引用傳值發生了什麽?
<?php $a = 1; $b = &$a;
3.6 引用傳值爲什麽影響了2個值
<?php $a = 1; $b = &$a; $b = 6;
内核修改zval的值時,發現is_ref_gc爲1,則直接修改該value,而不是複制一份.如下圖: