摘要:將會產生強制分裂結構體結構體引用數組時的一些奇怪現象引用數組時的怪現象數組不會比較細致的檢查,多維數組存在。因此,判斷的時候,只會判斷外面一層的結構體。中底層都離不開表。底層所有的變量都是放在中。
PHP編譯特點
編譯型語言
對于C語言,C++,編譯成機器碼(二進制)來運行。
Java語言,把.java 編譯成.class, 稱為bytecode(字節碼),由jvm來運行
解釋型語言
解釋器解釋執行。 典型的如: linux shell
解釋器逐行來執行命令
PHP執行
PHP是先編譯后執行
PHP稍有特殊,雖然是一個腳本語言,但不是靠解釋器解釋。而是zend虛擬機執行,屏蔽了操作系統的區別。
PHP代碼編譯成 opcode,由zend虛擬機來執行opcode。
但是opcode ,PHP腳本一結束,opcode就清除了。
opcode 能否緩存
PHP本身不支持,但是apc,xcache等加速器,實現了這樣的效果。
變量的底層實現PHP底層是C語言來實現的,C語言是強類型,而PHP是弱類型語言,是如何實現的
PHP的源碼包:
|__ ext |__ main |__ pear |__ sapi |__ tests |__ TSRM |__ Zend |__ .gdbinit
最核心的是Zend,這是zend虛擬的實現。包括棧,數據類型,編譯器等.
最重要的main,PHP的一些內建函數,最重要的函數都在該目錄下.
最大的一個目錄 ext, PHP的擴展.
PHP的大部分功能,都是以extenstion形式來完成的。
如果自身開發了一個擴展,也放入ext目錄下。
弱類型語言變量的實現
/* zend.h */ struct _zval_struct { zvalue_value value; /* 值 */ zend_uint refcount__gc; zend_uchar type; /* 活動類型 */ zend_uchar is_ref__gc; }
PHP中的一個變量,zend虛擬機中,使用的是 _zval_struct 的結構體來描述,變量的值也是一個就結構體來描述.
_zval_struct的結構體是由 四個字段/域 (可以理解成關聯數組)
zvalue_value value; /* 值 */
PHP變量的值,存儲這個字段中。
具體存儲的位置:
/* value 值 是一個 聯合 */ /* zend.h */ typedef union _zval_value { long lval; /* long value */ double dval; /* double value */ struct { char * val; int len; } str; HashTable *ht; /* hash table 指針 */ zend_object_value obj; } zvalue_value;
Zend對變量的表示
zend實現了 zval結構體
{ value: [聯合體] /* 聯合體的內容可能是C語言中的long,double,hashtable(*ht),obj, 聯合體只能是其中一種類型,是一個枚舉 */ type: 變量類型 , /* IS_NULL,IS_BOOL,IS_STRING, IS_LONG,IS_DOUBLE,IS_ARRAY,IS_OBJECT,IS_RESOURCE */ refcount_gc is_ref_gc }
C語言中類型對應PHP中的數據類型:
long -> int double -> double hashtable -> array struct -> string obj -> object
例如:
$a = 3; { value: [long lval = 3] type: IS_LONG } $a = 3.5; { value: [double dval = 3.5] type: IS_DOUBLE }變量類型的實現
zend_uchar type; /* 活動類型 */
可以根據上下文環境來強制轉換。
例如:需要echo 的時候 就轉換成 string
需要加減運算就 轉換成 int
PHP 中有8中數據類型,為什么zval->value 聯合體中,只有5中 ?
1: NULL,直接 zval->type = IS_NULL, 就可以表示,不必設置 value 的值。
2:BOOL, zval->type = IS_BOOL. 再設置 zval.value.lval = 1/0; (C語言中沒有布爾值,都是通過1,0,來表示)
3: resource ,資源型,往往是服務器上打開一個接口,如果 文件讀取接口。 zval->type = IS_RESOURCE, zval->type.lval = 服務器上打開的接口編號。
struct { char * val; int len; } str;
PHP中,字符串類型,長度是已經緩存的,調用strlen時,系統可以直接返回其長度,不需要計算。
$b = "hello"; /** * * { * union_zvalue { * // 字符串的指針 * struct{ * char: "hello"; * len: 5 * } str; * } * type: IS_STRING; * refcount_gc: 1, * is_ref_gc: 0 * } * */ //在PHP中字符串的長度,是直接體現在其結構體中,所以調用strlen(); 速度非常快,時間復雜度為0(1) echo strlen($b);符號表
符號表symbol_table,變量的花名冊
符號表是什么?
符號表示一張哈希表(哈希結構理解成關聯數組)
里面存儲了變量名-> 變量zval結構體的地址
struct _zend_executor_globals { ... ... HashTable * active_symbol_table /* 活動符號表 */ HashTable symbol_table /* 全局符號表 */ HashTable included_files; /* files already included */ }
// 變量花名冊 $a = 3; $b = 1.223; $c = "hello"; /** * * 生成了3個結構體 * 同時,全局符號表,中多了三條記錄 * * a ---> 0x123 ---> 結構體 { 3 } * b ---> 0x21a ---> 結構體 { 1.223 } * c ---> 0x1A0 ---> 結構體 { hello } * */ // 變量聲明 // 第一:結構體生成 // 第二:符號表中多了記錄,變量的花名冊 // 第三:指向結構體傳值賦值
傳值賦值發生了什么
在傳值賦值時:
以:$a = 3; $b = $a;為例:
并沒有再次產生結構體,而是2個變量共用1個結構體
此時,2個變量,指向同1個結構體
refcount_gc 值為 2 (如果沒有指針指引,會有垃圾回收機制清除)
cow寫時復制特性
$a = 3; $b = $a; /** * * 是否產生了2 個結構體? * 不是,共用1個, refcount_gc = 2; * */ $b = 5; echo $a, $b; // 3, 5 // $a,$b 指向同一個結構體,那么,修改$b或$a,對方會不會受干擾 ? 沒有干擾到對方。具有寫時復制的特性
如果有一方修改,將會造成結構體的分裂
結構體一開始共用,到某一方要修改值時,才分裂。這種特性稱為:COW 。Copy On Write。
引用賦值引用賦值發生了什么
當引用賦值時,雙方共用一個結構體(is_ref_gc=1)
關系圖例展示:
強制分裂1 的過程中(從0到1,表示想引用變量)。refcount_gc>1。多個變量共享一個變量值。將會產生強制分裂 /** * // $a $c 結構體 * { * value: 3; * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; * } * * // $b 結構體 * { * value: 3; * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; * } * */ $c = 5; // a c /** * value: 5 * type: IS_LONG; * refcount_gc: 2; * is_ref_gc: 1; */ // b /** * value: 3 * type: IS_LONG; * refcount_gc: 1; * is_ref_gc: 0; */ echo $a, $b, $c; // 5 , 3 , 5
引用數組時的一些奇怪現象
// 引用數組時的怪現象 $arr = array(0, 1, 2, 3); $tmp = $arr; $arr[1] = 11; echo $tmp[1]; // 1 // 數組不會比較細致的檢查,多維數組存在。 因此,判斷的時候,只會判斷外面 一層的 結構體。
數組不會比較細致的檢查
// 先 引用 后 賦值 $arr = array(0, 1, 2, 3); $x = &$arr[1]; $tmp = $arr; $arr[1] = 999; echo $tmp[1]; // 999 . hash表中的zvalue結構體中會變成引用類型。 // 只去關注外面一層結構體,而不去關注 hash表中的值。 echo "循環數組
"; // 先賦值,后引用 $arr = array(0, 1, 2, 3); $tmp = $arr; $x = &$arr[1]; $arr[1] = 999; echo $tmp[1]; // 1
循環數組時的怪現象
// 循環數組時的怪現象 $arr = array(0, 1, 2, 3); foreach ( $arr as $v ) { } var_dump(current($arr)); // 數組指針停留在數組結尾處, 取不到值. false echo "
"; $arr = array(0, 1, 2, 3); foreach ( $arr as $val=>$key ) { // foreach 使用的 $arr 是 $arr的副本. $arr[$key] = $val; // 修改之后,就會產生分裂。 foreach 遍歷的是 $arr 的副本。 但是原數組的指針已經走了一步. } var_dump(current($arr)); // 1
$arr = array("a", "b", "c", "d"); foreach ( $arr as &$val ) { // 該foreach 會導致 $val = &$arr[3]; } foreach ( $arr as $val ) { print_r($arr); echo "符號表與作用域
"; } // 兩個問題: // 數組使用時,要慎用引用。 // foreach 使用后,不會把數組的內部指針重置, 使用數組時,不要假想內部指針指向數組頭部. 也可以在foreach 之后 reset(); 指針。
當執行到函數時,會生成函數的“執行環境結構體”,包含函數名,參數,執行步驟,所在的類(如果是方法),以及為這個函數生成一個符號表。
符號表統一放在棧上,并把active_symbol_table指向剛產生的符號表。
// Zend/zend_compiles.h 文件中 // 源碼: struct _zend_execute_data { struct _zend_op *opline; zend_function_state function_state; zend_op_array *op_array; zval *object; 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; struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */ zval *delayed_exception; call_slot *call_slots; call_slot *call; };
// 簡化: struct _zend_execute_data { ... zend_op_array *op_array; // 函數的執行步驟. 如果是函數調用。是函數調用的后的opcode HashTable *symbol_table; // 此函數的符號表地址 zend_class_entry *current_scope; // 執行當前作用域 zval * current_this; // 對象 調用 this綁定 zval * current_object; // object 的指向 ... }
一個函數調用多次,會有多少個*op_array ?
一個函數產生 一個*op_array. 調用多次,會產生多個 環境結構體, 會依次入棧,然后順序執行。
調用多少次,就會入棧多少次。不同的執行環境,靠 唯一的 *op_array 來執行。
函數什么時候調用, 函數編譯后的 opcode 什么時候執行。
$age = 23; function t() { $age = 3; echo $age; } t(); /** * t 函數 在執行時,根據函數的參數,局部變量等,生成一個執行環境結構體。 * 結構體 入棧,函數編譯后的 opcode, 稱為 op_array (就是執行邏輯)。開始執行, 以入棧的環境結構體為環境來執行。 * 并生成此函數的 符號表, 函數尋找變量, 就在符號表中尋找。即局部變量。(一個環境結構體,就對應一張符號表) * * * 注意: 函數可能調用多次。棧中可能有某函數的多個執行環境 入棧。但是 op_array 只有一個。 * */靜態變量
靜態變量的實現
// Zend/zend_compile.h struct _zend_op_array { /* Common elements */ zend_uchar type; const char *function_name; zend_class_entry *scope; zend_uint fn_flags; union _zend_function *prototype; zend_uint num_args; zend_uint required_num_args; zend_arg_info *arg_info; /* END of common elements */ zend_uint *refcount; zend_op *opcodes; zend_uint last; zend_compiled_variable *vars; int last_var; zend_uint T; zend_uint nested_calls; zend_uint used_stack; zend_brk_cont_element *brk_cont_array; int last_brk_cont; zend_try_catch_element *try_catch_array; int last_try_catch; zend_bool has_finally_block; /* static variables support */ HashTable *static_variables; zend_uint this_var; const char *filename; zend_uint line_start; zend_uint line_end; const char *doc_comment; zend_uint doc_comment_len; zend_uint early_binding; /* the linked list of delayed declarations */ zend_literal *literals; int last_literal; void **run_time_cache; int last_cache_slot; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; };
// 簡化 struct _zend_op_array { ... HashTable *static_variables; // 靜態變量 ... }
編譯后的 op_array 只有一份。 靜態變量并沒有存儲在符號表(symbol_table)中.而是存放在op_array中。
function t() { static $age = 1; return $age += 1; } echo t(); echo t(); echo t(); // 靜態變量 不再和 執行的結構體, 也不再和 入棧的符號表有關。常量
// Zend/zend_constants.h // 常量結構體 typedef struct _zend_constant { zval value; // 變量結構體 int flags; // 標志,是否大小寫敏感等 char *name; // 常量名 uint name_len; // int module_number; // 模塊名 } zend_constant;define函數的實現
define函數當然是 調用zend_register_constant聲明的常量
具體如下:Zend/zend_builtin_functions.c
// 源碼:
ZEND_FUNCTION(define) { char *name; int name_len; zval *val; zval *val_free = NULL; zend_bool non_cs = 0; int case_sensitive = CONST_CS; zend_constant c; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|b", &name, &name_len, &val, &non_cs) == FAILURE) { return; } if(non_cs) { case_sensitive = 0; } /* class constant, check if there is name and make sure class is valid & exists */ if (zend_memnstr(name, "::", sizeof("::") - 1, name + name_len)) { zend_error(E_WARNING, "Class constants cannot be defined or redefined"); RETURN_FALSE; } repeat: switch (Z_TYPE_P(val)) { case IS_LONG: case IS_DOUBLE: case IS_STRING: case IS_BOOL: case IS_RESOURCE: case IS_NULL: break; case IS_OBJECT: if (!val_free) { if (Z_OBJ_HT_P(val)->get) { val_free = val = Z_OBJ_HT_P(val)->get(val TSRMLS_CC); goto repeat; } else if (Z_OBJ_HT_P(val)->cast_object) { ALLOC_INIT_ZVAL(val_free); if (Z_OBJ_HT_P(val)->cast_object(val, val_free, IS_STRING TSRMLS_CC) == SUCCESS) { val = val_free; break; } } } /* no break */ default: zend_error(E_WARNING,"Constants may only evaluate to scalar values"); if (val_free) { zval_ptr_dtor(&val_free); } RETURN_FALSE; } c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } 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; } }
// 關鍵代碼: c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* 大小寫敏感 */ c.name = str_strndup(name, name_len); if(c.name == NULL) { RETURN_FALSE; } 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; }
常量就一個符號(哈希)表. 都使用一個符號表。所以全局有效。
常量的生成
int zend_register_constant(zend_constant *c TSRMLS_DC) { ... ... zend_hash_add(EG(zend_constants), name, c->name_len, (vaid*)c,sizeof(zend_constant, NULL) == FAILURE); ... ... }
對象定義常量
class Dog { public $name = "kitty"; public function __toString () { return $this->name; } } $dog = new Dog(); define("DOG", $dog); print_r(DOG); /** * define 值為對象時,會把對象裝成標量來存儲,需要類有 __toString魔術方法 */對象
對象的底層實現
Zend/zend.h
struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; }; // zvalue 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; zend_ast *ast; } zvalue_value; // 在 zend.h 中 查看到 `zend_object_value obj;` 是以zend_object_value 定義. 在Zend/zend_types.h 文件中繼續查看 // Zend/zend_types.h
定義zend_object_value 結構體
typedef struct _zend_object_value { zend_object_handle handle; const zend_object_handlers *handlers; } zend_object_value;
通過new出來的對象,返回的是什么。是zend_object_value. 并不是真正的對象,而是對象的指針。
返回的 handle再次指向對象。
每次new一個對象,對象就存入一張hash表中。(形象的稱之為對象池)
對象存儲時的特點:
// 對象 class Dog { public $leg = 4; public $wei = 20; } $dog = new Dog(); // $dog 是一個對象么? // 嚴格說,并不是對象. /** * { * handle --指向--> [hash表 {leg: 4, wei: 20}] // hash表中存在 對象 * } */ $d2 = $dog; $d2->leg = 5; echo $dog->leg, "`", $d2->leg; // 5`5 // 對象并不是 引用賦值. 主要原因 zval 結構體 是再次指向一個hash表中的 對象池 $d2 = false; echo $dog->leg; // 5內存分層
內存管理與垃圾回收
PHP封裝了對系統內存的請求
不要直接使用malloc直接請求內存
PHP函數需要內存的時候,是通過emalloc,efree.
emalloc,efree向 mm_heap索要空間。
zend 中底層都離不開hash表。PHP中的HashTable太強大。
PHP 底層 所有的變量都是 放在 zend_mm_heap 中。 然后通過 各自的hash表來指向或跟蹤。
zend虛擬機的運行原理
PHP語法實現
Zend/zend_language_scanner.l Zend/zend_language_parser.y
OPcode編譯
Zend/zend.compile.c
執行引擎
Zend/zend_vm_* Zend/zend_execute.c
以apache模塊運行時的流程
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30378.html
摘要:前面寫過一篇底層分析關于寫時復制今天來講講關于強制分裂的知識,簡單來說,強制分裂就是在引用變量主動賦值前,該變量傳值賦值過,就會發生強制分裂。實際開發基本用不到這層原理,但在面試中強制分裂通常會和寫時復制一起考。 學習需要知其然而知其所以然,PHP底層相關就是這類知識。 前面寫過一篇《PHP底層分析:關于寫時復制(cow)》:https://segmentfault.com/a/119...
摘要:可以看到,該結構體存儲了關于變量值,有幾個變量指向該結構體,變量類型,是否為引用變量等信息。這個就是寫時復制,在作怪,他沒有在賦值的時候就分裂成兩個結構體,而是在我們改寫其中一個變量時發生效果,屬于一種慢復制也稱慢分裂。 想要走到技術的天花板,那么學習過程中在于知其然且知其所以然。 今天我們來討論一下PHP底層的寫時復制(也稱寫時分裂)。 首先我們先來看看一段代碼:showImg(ht...
摘要:中基礎中的三大坑,遍歷,引用機制,數組。今天我們在講講中的一些奇怪現象。本文適合有一定基礎的。運行流程共用一個結構體開始遍歷數組,進行判斷,拷貝數組是一個新的結構體,操作的是新的結構體。那么遍歷數組時,全程與原數組無關。 PHP中基礎中的三大坑,foreach遍歷,引用機制&,數組。 今天我們在講講foreach中的一些奇怪現象。 在講解之前,可以先看看我其他相關的文章,屬于同一個大的...
摘要:但是到底是如何找到對應的函數的呢今天,我們來一起尋找答案函數分類首先,我們先回顧一下的函數分類,函數包含用戶自定義函數內部函數匿名函數等多種類型。用戶自定義函數和內部函數編譯完成后會將函數名注冊到全局函數列表中。 對于PHPer而言,我們通常會把常用的功能封裝成一個函數來進行復用,以提升開發效率。但是php到底是如何找到對應的函數的呢?今天,我們來一起尋找答案~ 函數分類 首先,我們先...
閱讀 2117·2021-11-19 09:58
閱讀 1707·2021-11-15 11:36
閱讀 2874·2019-08-30 15:54
閱讀 3394·2019-08-29 15:07
閱讀 2764·2019-08-26 11:47
閱讀 2812·2019-08-26 10:11
閱讀 2503·2019-08-23 18:22
閱讀 2750·2019-08-23 17:58