cataloguephp
1. PHP Hash表 2. PHP數組定義 3. PHP變量實現 4. PHP常量實現
1. PHP Hash表html
0x1: 基本概念node
哈希表在實踐中使用的很是普遍,例如編譯器一般會維護的一個符號表來保存標記,不少高級語言中也顯式的支持哈希表。 哈希表一般提供查找(Search),插入(Insert),刪除(Delete)等操做,這些操做在最壞的狀況
下和鏈表的性能同樣爲O(n)。 不過一般並不會這麼壞,合理設計的哈希算法能有效的避免這類狀況,一般哈希表的這些操做時間複雜度爲O(1)。 這也是它被鍾愛的緣由
正是由於哈希表在使用上的便利性及效率上的表現,目前大部分動態語言的實現中都使用了哈希表
哈希表是一種經過哈希函數,將特定的鍵映射到特定值的一種數據結構,它維護鍵和值之間一一對應關係mysql
1. 鍵(key): 用於操做數據的標示,例如PHP數組中的索引,或者字符串鍵等等 2. 槽(slot/bucket): 哈希表中用於保存數據的一個單元,也就是數據真正存放的容器 3. 哈希函數(hash function): 將key映射(map)到數據應該存放的slot所在位置的函數 4. 哈希衝突(hash collision): 哈希函數將兩個不一樣的key映射到同一個索引的狀況
哈希表能夠理解爲數組的擴展或者關聯數組,數組使用數字下標來尋址,若是關鍵字(key)的範圍較小且是數字的話, 咱們能夠直接使用數組來完成哈希表,而若是關鍵字範圍太大,若是直接使用數組咱們須要爲全部可能的key申請空間。 不少狀況下這是不現實的。即便空間足夠,空間利用率也會很低,這並不理想。同時鍵也可能並非數字, 在PHP中尤其如此,因此人們使用一種映射函數(哈希函數)來將key映射到特定的域中git
h(key) -> index
經過合理設計的哈希函數,咱們就能將key映射到合適的範圍,由於咱們的key空間能夠很大(例如字符串key), 在映射到一個較小的空間中時可能會出現兩個不一樣的key映射被到同一個index上的狀況, 這就是咱們所說的出現了衝突。 目前解決hash衝突的方法主要有兩種:連接法和開放尋址法github
1. 衝突解決: 連接法算法
連接法經過使用一個鏈表來保存slot值的方式來解決衝突,也就是當不一樣的key映射到一個槽中的時候使用鏈表來保存這些值。 因此使用連接法是在最壞的狀況下,也就是全部的key都映射到同一個槽中了,這樣哈希表就退化成了一個鏈表, 這樣的話操做鏈表的時間複雜度則成了O(n),這樣哈希表的性能優點就沒有了, 因此選擇一個合適的哈希函數是最爲關鍵的
因爲目前大部分的編程語言的哈希表實現都是開源的,大部分語言的哈希算法都是公開的算法, 雖然目前的哈希算法都能良好的將key進行比較均勻的分佈,而這個假使的前提是key是隨機的,正是因爲算法的肯定性, 這就致使了別有用心的黑客能利用已知算法的可肯定性來構造一些特殊的key,讓這些key都映射到同一個槽位致使哈希表退化成單鏈表,致使程序的性能急劇降低,從而形成一些應用的吞吐能力急劇降低, 尤爲是對於高併發的應用影響很大,經過大量相似的請求可讓服務器遭受DoS(服務拒絕攻擊)
哈希衝突攻擊利用的哈希表最根本的弱點是:sql
開源算法和哈希實現的肯定性以及可預測性, 這樣攻擊者才能夠利用特殊構造的key來進行攻擊。要解決這個問題的方法則是讓攻擊者沒法輕易構造 可以進行攻擊的key序列
目前PHP中HashTable的哈希衝突解決方法就是連接法編程
2. 衝突解決: 開放尋址法
api
一般還有另一種解決衝突的方法:開放尋址法。使用開放尋址法是槽自己直接存放數據,在插入數據時若是key所映射到的索引已經有數據了,這說明發生了衝突,這是會尋找下一個槽, 若是該槽也被佔用了則繼續尋找下一個槽,直到尋找到沒有被佔用的槽,在查找時也使用一樣的策略來進行
因爲開放尋址法處理衝突的時候佔用的是其餘槽位的空間,這可能會致使後續的key在插入的時候更加容易出現 哈希衝突,因此採用開放尋址法的哈希表的裝載因子不能過高,不然容易出現性能降低
裝載因子是哈希表保存的元素數量和哈希表容量的比 1. 一般採用連接法解決衝突的哈希表的裝載,因子最好不要大於1(等於1意味着Hash表已經存滿了,接下來開始保存的鍵值都會致使衝突發生即鏈表增加,而鏈表的效率是低於Hash表的) 2. 而採用開放尋址法的哈希表最好不要大於0.5
0x2: 哈希表的實現
實現一個哈希表也很容易,主要須要完成的工做只有三點
1. 實現哈希函數 2. 衝突的解決 3. 操做接口的實現
在開始學習PHP原生內核的Hash表實現前,咱們本身能夠先手工實現一個簡易版的Hash表
1. 基礎數據結構定義
#ifndef _HASH_TABLE_H_ #define _HASH_TABLE_H_ 1 typedef struct _Bucket { char *key; void *value; struct _Bucket *next; } Bucket; typedef struct _HashTable { int size; //HashTable size/lines int elem_num; //total elements count Bucket** buckets; } HashTable; #endif
2. 哈希函數實現
哈希函數須要儘量的將不一樣的key映射到不一樣的槽(slot或者bucket)中,首先咱們採用一種最爲簡單的哈希算法實現: 將key字符串的全部字符加起來,而後以結果對哈希表的大小取模,這樣索引就能落在數組索引的範圍以內了
//hashtable.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include "hashtable.h" int hash_str(char *key); int hash_str(char *key) { int hash = 0; char *cur = key; while(*cur != '\0') { hash += *cur; ++cur; } return hash; } //hashtable.h #define HASH_INDEX(ht, key) (hash_str((key)) % (ht)->size)
3. 操做接口的實現
爲了操做哈希表,實現了以下幾個操做接口函數
int hash_init(HashTable *ht); // 初始化哈希表 int hash_lookup(HashTable *ht, char *key, void **result); // 根據key查找內容 int hash_insert(HashTable *ht, char *key, void *value); // 將內容插入到哈希表中 int hash_remove(HashTable *ht, char *key); // 刪除key所指向的內容 int hash_destroy(HashTable *ht);
4. 完整源代碼
hashtable.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "hashtable.h" static void resize_hash_table_if_needed(HashTable *ht); static int hash_str(char *key); int hash_init(HashTable *ht) { ht->size = HASH_TABLE_INIT_SIZE; ht->elem_num = 0; ht->buckets = (Bucket **)calloc(ht->size, sizeof(Bucket *)); if(ht->buckets == NULL) return FAILED; LOG_MSG("[init]\tsize: %i\n", ht->size); return SUCCESS; } int hash_lookup(HashTable *ht, char *key, void **result) { int index = HASH_INDEX(ht, key); Bucket *bucket = ht->buckets[index]; if(bucket == NULL) goto failed; while(bucket) { if(strcmp(bucket->key, key) == 0) { LOG_MSG("[lookup]\t found %s\tindex:%i value: %p\n", key, index, bucket->value); *result = bucket->value; return SUCCESS; } bucket = bucket->next; } failed: LOG_MSG("[lookup]\t key:%s\tfailed\t\n", key); return FAILED; } int hash_insert(HashTable *ht, char *key, void *value) { // check if we need to resize the hashtable resize_hash_table_if_needed(ht); int index = HASH_INDEX(ht, key); Bucket *org_bucket = ht->buckets[index]; Bucket *tmp_bucket = org_bucket; // check if the key exits already while(tmp_bucket) { if(strcmp(key, tmp_bucket->key) == 0) { LOG_MSG("[update]\tkey: %s\n", key); tmp_bucket->value = value; return SUCCESS; } tmp_bucket = tmp_bucket->next; } Bucket *bucket = (Bucket *)malloc(sizeof(Bucket)); bucket->key = key; bucket->value = value; bucket->next = NULL; ht->elem_num += 1; if(org_bucket != NULL) { LOG_MSG("[collision]\tindex:%d key:%s\n", index, key); bucket->next = org_bucket; } ht->buckets[index]= bucket; LOG_MSG("[insert]\tindex:%d key:%s\tht(num:%d)\n", index, key, ht->elem_num); return SUCCESS; } int hash_remove(HashTable *ht, char *key) { int index = HASH_INDEX(ht, key); Bucket *bucket = ht->buckets[index]; Bucket *prev = NULL; if(bucket == NULL) return FAILED; // find the right bucket from the link list while(bucket) { if(strcmp(bucket->key, key) == 0) { LOG_MSG("[remove]\tkey:(%s) index: %d\n", key, index); if(prev == NULL) { ht->buckets[index] = bucket->next; } else { prev->next = bucket->next; } free(bucket); return SUCCESS; } prev = bucket; bucket = bucket->next; } LOG_MSG("[remove]\t key:%s not found remove \tfailed\t\n", key); return FAILED; } int hash_destroy(HashTable *ht) { int i; Bucket *cur = NULL; Bucket *tmp = NULL; for(i=0; i < ht->size; ++i) { cur = ht->buckets[i]; while(cur) { tmp = cur; cur = cur->next; free(tmp); } } free(ht->buckets); return SUCCESS; } static int hash_str(char *key) { int hash = 0; char *cur = key; while(*cur != '\0') { hash += *cur; ++cur; } return hash; } static int hash_resize(HashTable *ht) { // double the size int org_size = ht->size; ht->size = ht->size * 2; ht->elem_num = 0; LOG_MSG("[resize]\torg size: %i\tnew size: %i\n", org_size, ht->size); Bucket **buckets = (Bucket **)calloc(ht->size, sizeof(Bucket **)); Bucket **org_buckets = ht->buckets; ht->buckets = buckets; int i = 0; for(i=0; i < org_size; ++i) { Bucket *cur = org_buckets[i]; Bucket *tmp; while(cur) { // rehash: insert again hash_insert(ht, cur->key, cur->value); // free the org bucket, but not the element tmp = cur; cur = cur->next; free(tmp); } } free(org_buckets); LOG_MSG("[resize] done\n"); return SUCCESS; } // if the elem_num is almost as large as the capacity of the hashtable // we need to resize the hashtable to contain enough elements static void resize_hash_table_if_needed(HashTable *ht) { if(ht->size - ht->elem_num < 1) { hash_resize(ht); } }
hashtable.h
#ifndef _HASH_TABLE_H_ #define _HASH_TABLE_H_ 1 #define HASH_TABLE_INIT_SIZE 6 #define HASH_INDEX(ht, key) (hash_str((key)) % (ht)->size) #if defined(DEBUG) # define LOG_MSG printf #else # define LOG_MSG(...) #endif #define SUCCESS 0 #define FAILED -1 typedef struct _Bucket { char *key; void *value; struct _Bucket *next; } Bucket; typedef struct _HashTable { int size; // 哈希表的大小 int elem_num; // 已經保存元素的個數 Bucket **buckets; } HashTable; int hash_init(HashTable *ht); int hash_lookup(HashTable *ht, char *key, void **result); int hash_insert(HashTable *ht, char *key, void *value); int hash_remove(HashTable *ht, char *key); int hash_destroy(HashTable *ht); #endif
main.c
#include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string.h> #include "hashtable.h" #define TEST(tcase) printf(">>> [START CASE] " tcase "<<<\n") #define PASS(tcase) printf(">>> [PASSED] " tcase " <<<\n") int main(int argc, char **argv) { HashTable *ht = (HashTable *)malloc(sizeof(HashTable)); int result = hash_init(ht); assert(result == SUCCESS); /* Data */ int int1 = 10; int int2 = 20; char str1[] = "Hello TIPI"; char str2[] = "Value"; /* to find data container */ int *j = NULL; char *find_str = NULL; /* Test Key insert */ TEST("Key insert"); hash_insert(ht, "KeyInt", &int1); hash_insert(ht, "asdfKeyStrass", str1); hash_insert(ht, "K13eyStras", str1); hash_insert(ht, "KeyStr5", str1); hash_insert(ht, "KeyStr", str1); PASS("Key insert"); /* Test key lookup */ TEST("Key lookup"); hash_lookup(ht, "KeyInt", (void **)&j); hash_lookup(ht, "KeyStr", (void **)&find_str); assert(strcmp(find_str, str1) == 0); assert(*j = int1); PASS("Key lookup"); /* Test Key update */ TEST("Test key update"); hash_insert(ht, "KeyInt", &int2); hash_lookup(ht, "KeyInt", (void **)&j); assert(*j = int2); PASS("Test key update"); TEST(">>> Test key not found <<< "); result = hash_lookup(ht, "non-exits-key", (void **)&j); assert(result == FAILED); PASS("non-exist-key lookup"); TEST("Test key not found after remove"); char strMyKey[] = "My-Key-Value"; find_str = NULL; hash_insert(ht, "My-Key", &strMyKey); result = hash_remove(ht, "My-Key"); assert(result == SUCCESS); result = hash_lookup(ht, "My-Key", (void **)&find_str); assert(find_str == NULL); assert(result == FAILED); PASS("Test key not found after remove"); PASS(">>> Test key not found <<< "); TEST("Add many elements and make hashtable rehash"); hash_insert(ht, "a1", &int2); hash_insert(ht, "a2", &int1); hash_insert(ht, "a3", &int1); hash_insert(ht, "a4", &int1); hash_insert(ht, "a5", &int1); hash_insert(ht, "a6", &int1); hash_insert(ht, "a7", &int1); hash_insert(ht, "a8", str2); hash_insert(ht, "a9", &int1); hash_insert(ht, "a10", &int1); hash_insert(ht, "a11", &int1); hash_insert(ht, "a12", &int1); hash_insert(ht, "a13", &int1); hash_insert(ht, "a14", &int1); hash_insert(ht, "a15", &int1); hash_insert(ht, "a16", &int1); hash_insert(ht, "a17", &int1); hash_insert(ht, "a18", &int1); hash_insert(ht, "a19", &int1); hash_insert(ht, "a20", &int1); hash_insert(ht, "a21", &int1); hash_insert(ht, "a22", &int1); hash_insert(ht, "a23", &int1); hash_insert(ht, "a24", &int1); hash_insert(ht, "a24", &int1); hash_insert(ht, "a24", &int1); hash_insert(ht, "a25", &int1); hash_insert(ht, "a26", &int1); hash_insert(ht, "a27", &int1); hash_insert(ht, "a28", &int1); hash_insert(ht, "a29", &int1); hash_insert(ht, "a30", &int1); hash_insert(ht, "a31", &int1); hash_insert(ht, "a32", &int1); hash_insert(ht, "a33", &int1); hash_lookup(ht, "a23", (void **)&j); assert(*j = int1); hash_lookup(ht, "a30", (void **)&j); assert(*j = int1); PASS("Add many elements and make hashtable rehash"); hash_destroy(ht); free(ht); printf("Woohoo, It looks like HashTable works properly\n"); return 0; }
編譯運行
gcc -g -Wall -DDEBUG -o a.out main.c hashtable.c
0x3: 數據結構
在PHP中全部的數據,變量、常量、類、屬性、數組都用Hash表來實現
\php-5.6.17\Zend\zend_hash.h
typedef struct bucket { ulong h; /* Used for numeric indexing */ uint nKeyLength; //key長度 void *pData; //指向 Bucke保存的數據 指針 void *pDataPtr; //指針數據 struct bucket *pListNext; //下一個元素指針 struct bucket *pListLast; //上一個元素指針 struct bucket *pNext; struct bucket *pLast; const char *arKey; } Bucket; typedef struct _hashtable { uint nTableSize; //HashTable的大小 uint nTableMask; //等於nTableSize-1 uint nNumOfElements; //對象個數 ulong nNextFreeElement; //指向下一個空元素位置 nTableSize+1 Bucket *pInternalPointer; /* Used for element traversal 保存當前遍歷的指針 */ Bucket *pListHead; //頭元素指針 Bucket *pListTail; //尾元素指針 Bucket **arBuckets; //存儲hash數組數據 dtor_func_t pDestructor; //相似於析構函數 zend_bool persistent; //用哪一種方法分配內存空間 PHP統一管理內存仍是用普通的malloc unsigned char nApplyCount; //當前hash bucket被訪問的次數,是否遍歷過數據,防止無限遞歸循環 zend_bool bApplyProtection; #if ZEND_DEBUG int inconsistent; #endif } H
Relevant Link:
http://www.imsiren.com/archives/6 http://www.php-internals.com/book/?p=chapt03/03-01-01-hashtable https://github.com/reeze/tipi/tree/master/book/sample/chapt03/03-01-01-hashtable
2. PHP數組定義
PHP中的數組其實是一個有序映射。映射是一種把 values 關聯到 keys 的類型。此類型在不少方面作了優化,所以能夠把它當成
1. 真正的數組 2. 列表(向量) 3. 散列表(是映射的一種實現) 4. 字典 5. 集合 6. 棧 7. 隊列以及更多可能性
數組元素的值也能夠是另外一個數組。樹形結構和多維數組也是容許的,PHP中常用數組,使用數組最大的好處即是速度!讀寫均可以在O(1)內完成,由於它每一個元素的大小都是一致的,只要知道下標,即可以瞬間計算出其對應的元素在內存中的位置,從而直接取出或者寫入
PHP大部分功能,都是經過HashTable來實現,其中就包括數組
HashTable即具備雙向鏈表的優勢,PHP中的定義的變量保存在一個符號表裏,而這個符號表其實就是一個HashTable,它的每個元素都是一個zval*類型的變量。不只如此,保存用戶定義的函數、類、資源等的容器都是以HashTable的形式在內核中實現的
所以,PHP的數組讀寫均可以在O(1)內完成,這是很是高效的,所以開銷和C++、Java相比也就是hashtable的建立了,咱們看一下PHP定義數組
<?php $array = array(); $array["key"] = "values"; ?>
在內核中使用宏來實現
0x1: 數組初始化
Zend/zend_vm_execute.h
static int ZEND_FASTCALL ZEND_INIT_ARRAY_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE array_init(&EX_T(opline->result.var).tmp_var); if (IS_CV == IS_UNUSED) { ZEND_VM_NEXT_OPCODE(); #if 0 || IS_CV != IS_UNUSED } else { return ZEND_ADD_ARRAY_ELEMENT_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); #endif } }
\php-5.6.17\Zend\zend_API.c
ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC) /* {{{ */ { ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg)); _zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC); Z_TYPE_P(arg) = IS_ARRAY; return SUCCESS; }
\php-5.6.17\Zend\zend_hash.c
ZEND_API int _zend_hash_init(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent ZEND_FILE_LINE_DC) { uint i = 3; SET_INCONSISTENT(HT_OK); if (nSize >= 0x80000000) { /* prevent overflow */ //HASH表大小大於0x80000000則初始化爲0x80000000 ht->nTableSize = 0x80000000; } else { while ((1U << i) < nSize) { i++; } //申請的數組Size空間調整爲2的n次方,這樣有利於內存對齊,i=3,nTableSize最小值爲8 ht->nTableSize = 1 << i; } ht->nTableMask = 0; /* 0 means that ht->arBuckets is uninitialized */ ht->pDestructor = pDestructor; //一個函數指針,當HashTable發生增,刪,改時調用 ht->arBuckets = (Bucket**)&uninitialized_bucket; ht->pListHead = NULL; ht->pListTail = NULL; ht->nNumOfElements = 0; ht->nNextFreeElement = 0; ht->pInternalPointer = NULL; ht->persistent = persistent; //若是persisient爲TRUE,則使用操做系統自己的內存分配函數爲Bucket分配內存,不然使用PHP的內存分配函數 ht->nApplyCount = 0; ht->bApplyProtection = 1; return SUCCESS; } ZEND_API int _zend_hash_init_ex(HashTable *ht, uint nSize, dtor_func_t pDestructor, zend_bool persistent, zend_bool bApplyProtection ZEND_FILE_LINE_DC) { int retval = _zend_hash_init(ht, nSize, pDestructor, persistent ZEND_FILE_LINE_CC); ht->bApplyProtection = bApplyProtection; return retval; }
0x2: 數組添加鍵值
以三種典型的鍵值插入/取值操做爲例
<?php $patterns = array(); $patterns[0] = '!quick!e'; $patterns['1'] = '{brown}'; $patterns['string'] = 'littlehann'; echo $patterns[0]; echo $patterns'1']; echo $patterns['string']; foreach($patterns as $Item){ echo $Item; }
1. 內核對PHP添加數字索引的處理方式
對int型數字鍵的操做來講,在PHP中無論是對數組的添加操做(zend_hash_add),仍是對數組的更新操做(zend_hash_update),其最終都是調用_zend_hash_add_or_update函數完成
\php-5.6.17\Zend\zend_hash.c
ZEND_API int _zend_hash_index_update_or_next_insert(HashTable *ht, ulong h, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC) { uint nIndex; Bucket *p; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); CHECK_INIT(ht); if (flag & HASH_NEXT_INSERT) { h = ht->nNextFreeElement; } //生成hash值,經過與nTableMask執行與操做,獲取在arBuckets數組中的Bucket位置 nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; //若是Bucket中已經存在元素,則遍歷整個Bucket鏈表(HASH極可能衝突),查找是否存在相同的key值元素,若是有而且是update調用,則執行update數據操做 while (p != NULL) { if ((p->nKeyLength == 0) && (p->h == h)) { if (flag & HASH_NEXT_INSERT || flag & HASH_ADD) { return FAILURE; } ZEND_ASSERT(p->pData != pData); HANDLE_BLOCK_INTERRUPTIONS(); if (ht->pDestructor) { ht->pDestructor(p->pData); } UPDATE_DATA(ht, p, pData, nDataSize); HANDLE_UNBLOCK_INTERRUPTIONS(); if (pDest) { *pDest = p->pData; } return SUCCESS; } p = p->pNext; } //當前數組中沒有該鍵,建立新的Bucket元素,初始化數據 p = (Bucket *) pemalloc_rel(sizeof(Bucket), ht->persistent); p->arKey = NULL; p->nKeyLength = 0; /* Numeric indices are marked by making the nKeyLength == 0 */ p->h = h; INIT_DATA(ht, p, pData, nDataSize); if (pDest) { *pDest = p->pData; } //將新元素添加到當前hash值對應的Bucket鏈表的最前面(CONNECT_TO_BUCKET_DLLIST),即放在第一個的位置 CONNECT_TO_BUCKET_DLLIST(p, ht->arBuckets[nIndex]); HANDLE_BLOCK_INTERRUPTIONS(); ht->arBuckets[nIndex] = p; //將新的Bucket元素添加到數組的連接表的最後面(CONNECT_TO_GLOBAL_DLLIST),即隊列的FIFO CONNECT_TO_GLOBAL_DLLIST(p, ht); HANDLE_UNBLOCK_INTERRUPTIONS(); //PHP中能夠不指定索引值向數組中添加元素,這時將默認使用數字做爲索引,和C語言中的枚舉相似 //當不指定索引時,新插入的元素的索引由nNextFreeElement字段決定了 //若是數組中存在了數字key,則會默認使用最新使用的key + 1,例如上例中已經存在了10做爲key的元素,這樣新插入的默認索引就爲11 if ((long)h >= (long)ht->nNextFreeElement) { ht->nNextFreeElement = h < LONG_MAX ? h + 1 : LONG_MAX; } ht->nNumOfElements++; //當HASH表到達當前上限時(2次方對齊的上限),進行擴容,而且將全部元素鍵從新進行哈希並進行索引映射 ZEND_HASH_IF_FULL_DO_RESIZE(ht); return SUCCESS; }
2. 內核對PHP中字符串索引的處理方式
與數字索引相比,多了一步將字符串轉換爲整型。用到的算法是time33,即對字符串的每一個字符轉換爲ASCII碼乘上33而且相加獲得的結果
\php-5.6.17\Zend\zend_hash.c
ZEND_API int _zend_hash_add_or_update(HashTable *ht, const char *arKey, uint nKeyLength, void *pData, uint nDataSize, void **pDest, int flag ZEND_FILE_LINE_DC) { ulong h; uint nIndex; Bucket *p; #ifdef ZEND_SIGNALS TSRMLS_FETCH(); #endif IS_CONSISTENT(ht); ZEND_ASSERT(nKeyLength != 0); CHECK_INIT(ht); //調用zend_inline_hash_func將字符串key,嘗試轉化爲int值 //後續的代碼邏輯和int型的array操做一致 h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask;
\php-5.6.17\Zend\zend_hash.h
static inline ulong zend_inline_hash_func(const char *arKey, uint nKeyLength) { register ulong hash = 5381; /* variant with the hash unrolled eight times */ for (; nKeyLength >= 8; nKeyLength -= 8) { hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; hash = ((hash << 5) + hash) + *arKey++; } switch (nKeyLength) { case 7: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 6: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 5: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 4: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 3: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 2: hash = ((hash << 5) + hash) + *arKey++; /* fallthrough... */ case 1: hash = ((hash << 5) + hash) + *arKey++; break; case 0: break; EMPTY_SWITCH_DEFAULT_CASE() } return hash; }
Relevant Link:
http://www.kancloud.cn/kancloud/php-internals/42760 http://blog.csdn.net/a600423444/article/details/8850617 http://www.imsiren.com/archives/250
0x3: 操做PHP數組的API
//初始化PHP數組 array_init(zval *arg); array_init_size(zval *arg, uint size); //關聯數組賦值的操做函數,等同於$array[$stringKey] = $value; add_assoc_null(zval *aval, char *key); add_assoc_bool(zval *aval, char *key, zend_bool bval); add_assoc_long(zval *aval, char *key, long lval); add_assoc_double(zval *aval, char *key, double dval); add_assoc_string(zval *aval, char *key, char *strval, int dup); add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup); add_assoc_zval(zval *aval, char *key, zval *value); //上述函數均爲宏函數,都是對add_assoc_*_ex函數的封裝 //數字索引數組賦值的操做函數,等效於$array[$numKey] = $value; ZEND_API int add_index_long(zval *arg, ulong idx, long n); ZEND_API int add_index_null(zval *arg, ulong idx); ZEND_API int add_index_bool(zval *arg, ulong idx, int b); ZEND_API int add_index_resource(zval *arg, ulong idx, int r); ZEND_API int add_index_double(zval *arg, ulong idx, double d); ZEND_API int add_index_string(zval *arg, ulong idx, const char *str, int duplicate); ZEND_API int add_index_stringl(zval *arg, ulong idx, const char *str, uint length, int duplicate); ZEND_API int add_index_zval(zval *arg, ulong index, zval *value); //使用內置數字索引的數組賦值的操做函數,等效於$array[] = $value; ZEND_API int add_next_index_long(zval *arg, long n); ZEND_API int add_next_index_null(zval *arg); ZEND_API int add_next_index_bool(zval *arg, int b); ZEND_API int add_next_index_resource(zval *arg, int r); ZEND_API int add_next_index_double(zval *arg, double d); ZEND_API int add_next_index_string(zval *arg, const char *str, int duplicate); ZEND_API int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate); ZEND_API int add_next_index_zval(zval *arg, zval *value); //數組元素賦值並返回,等效於{ $array[$key] = $value; return $value; } ZEND_API int add_get_assoc_string_ex(zval *arg, const char *key, uint key_len, const char *str, void **dest, int duplicate); ZEND_API int add_get_assoc_stringl_ex(zval *arg, const char *key, uint key_len, const char *str, uint length, void **dest, int duplicate); #define add_get_assoc_string(__arg, __key, __str, __dest, __duplicate) add_get_assoc_string_ex(__arg, __key, strlen(__key)+1, __str, __dest, __duplicate) #define add_get_assoc_stringl(__arg, __key, __str, __length, __dest, __duplicate) add_get_assoc_stringl_ex(__arg, __key, strlen(__key)+1, __str, __length, __dest, __duplicate) ZEND_API int add_get_index_long(zval *arg, ulong idx, long l, void **dest); ZEND_API int add_get_index_double(zval *arg, ulong idx, double d, void **dest); ZEND_API int add_get_index_string(zval *arg, ulong idx, const char *str, void **dest, int duplicate); ZEND_API int add_get_index_stringl(zval *arg, ulong idx, const char *str, uint length, void **dest, int duplicate);
0x4: 數組的foreach遍歷
在詞法分析階段,foreach會被識別爲一個TOKEN:T_FOREACH
\php-5.6.17\Zend\zend_language_parser.y
T_FOREACH '(' variable T_AS { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 1 TSRMLS_CC); } foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); } foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); } | T_FOREACH '(' expr_without_variable T_AS { zend_do_foreach_begin(&$1, &$2, &$3, &$4, 0 TSRMLS_CC); } foreach_variable foreach_optional_arg ')' { zend_do_foreach_cont(&$1, &$2, &$4, &$6, &$7 TSRMLS_CC); } foreach_statement { zend_do_foreach_end(&$1, &$4 TSRMLS_CC); }
實現foreach的核心就是以下3個函數
zend_do_foreach_begin
zend_do_foreach_cont
zend_do_foreach_end
1. zend_do_foreach_begin
\php-5.6.17\Zend\zend_compile.c
void zend_do_foreach_begin(znode *foreach_token, znode *open_brackets_token, znode *array, znode *as_token, int variable TSRMLS_DC) /* {{{ */ { zend_op *opline; zend_bool is_variable; zend_op dummy_opline; if (variable) { if (zend_is_function_or_method_call(array)) { is_variable = 0; } else { is_variable = 1; } /* save the location of FETCH_W instruction(s) */ open_brackets_token->u.op.opline_num = get_next_op_number(CG(active_op_array)); zend_do_end_variable_parse(array, BP_VAR_W, 0 TSRMLS_CC); if (zend_is_function_or_method_call(array)) { opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = ZEND_SEPARATE; SET_NODE(opline->op1, array); SET_UNUSED(opline->op2); opline->result_type = IS_VAR; opline->result.var = opline->op1.var; } } else { is_variable = 0; open_brackets_token->u.op.opline_num = get_next_op_number(CG(active_op_array)); } /* save the location of FE_RESET */ //記錄當前的opline行數(爲之後跳轉而記錄)1 foreach_token->u.op.opline_num = get_next_op_number(CG(active_op_array)); opline = get_next_op(CG(active_op_array) TSRMLS_CC); /* Preform array reset */ //對數組進行RESET(將內部指針指向第一個元素) opline->opcode = ZEND_FE_RESET; opline->result_type = IS_VAR; //獲取臨時變量($val) opline->result.var = get_temporary_variable(CG(active_op_array)); SET_NODE(opline->op1, array); SET_UNUSED(opline->op2); opline->extended_value = is_variable ? ZEND_FE_RESET_VARIABLE : 0; COPY_NODE(dummy_opline.result, opline->result); zend_stack_push(&CG(foreach_copy_stack), (void *) &dummy_opline, sizeof(zend_op)); /* save the location of FE_FETCH */ as_token->u.op.opline_num = get_next_op_number(CG(active_op_array)); //設置獲取變量的OPCODE FE_FETCH,結果存第3步的臨時變量 opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = ZEND_FE_FETCH; opline->result_type = IS_VAR; opline->result.var = get_temporary_variable(CG(active_op_array)); COPY_NODE(opline->op1, dummy_opline.result); opline->extended_value = 0; SET_UNUSED(opline->op2); //記錄獲取變量的OPCODES的行數 opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = ZEND_OP_DATA; SET_UNUSED(opline->op1); SET_UNUSED(opline->op2); SET_UNUSED(opline->result); } /* }}} */
2. zend_do_foreach_cont
void zend_do_foreach_cont(znode *foreach_token, const znode *open_brackets_token, const znode *as_token, znode *value, znode *key TSRMLS_DC) /* {{{ */ { zend_op *opline; znode dummy, value_node; zend_bool assign_by_ref=0; opline = &CG(active_op_array)->opcodes[as_token->u.op.opline_num]; if (key->op_type != IS_UNUSED) { znode *tmp; /* switch between the key and value... */ tmp = key; key = value; value = tmp; /* Mark extended_value in case both key and value are being used */ opline->extended_value |= ZEND_FE_FETCH_WITH_KEY; } if ((key->op_type != IS_UNUSED)) { if (key->EA & ZEND_PARSED_REFERENCE_VARIABLE) { zend_error_noreturn(E_COMPILE_ERROR, "Key element cannot be a reference"); } if (key->EA & ZEND_PARSED_LIST_EXPR) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use list as key element"); } } //根據foreach_variable的u.EA.type來判斷是否引用 if (value->EA & ZEND_PARSED_REFERENCE_VARIABLE) { assign_by_ref = 1; /* Mark extended_value for assign-by-reference */ opline->extended_value |= ZEND_FE_FETCH_BYREF; CG(active_op_array)->opcodes[foreach_token->u.op.opline_num].extended_value |= ZEND_FE_RESET_REFERENCE; } else { //根據是否引用來調整zend_do_foreach_begin中生成的FE_FETCH方式 zend_op *fetch = &CG(active_op_array)->opcodes[foreach_token->u.op.opline_num]; zend_op *end = &CG(active_op_array)->opcodes[open_brackets_token->u.op.opline_num]; /* Change "write context" into "read context" */ fetch->extended_value = 0; /* reset ZEND_FE_RESET_VARIABLE */ while (fetch != end) { --fetch; if (fetch->opcode == ZEND_FETCH_DIM_W && fetch->op2_type == IS_UNUSED) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use [] for reading"); } if (fetch->opcode == ZEND_SEPARATE) { MAKE_NOP(fetch); } else { fetch->opcode -= 3; /* FETCH_W -> FETCH_R */ } } } //根據zend_do_foreach_begin中記錄的取變量的OPCODES的行數 GET_NODE(&value_node, opline->result); //獲取HashTable的頭指針 if (value->EA & ZEND_PARSED_LIST_EXPR) { if (!CG(list_llist).head) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot use empty list"); } //遍歷HashTable雙鏈表 zend_do_list_end(&dummy, &value_node TSRMLS_CC); zend_do_free(&dummy TSRMLS_CC); } else { if (assign_by_ref) { zend_do_end_variable_parse(value, BP_VAR_W, 0 TSRMLS_CC); /* Mark FE_FETCH as IS_VAR as it holds the data directly as a value */ zend_do_assign_ref(NULL, value, &value_node TSRMLS_CC); } else { zend_do_assign(&dummy, value, &value_node TSRMLS_CC); zend_do_free(&dummy TSRMLS_CC); } } if (key->op_type != IS_UNUSED) { znode key_node; opline = &CG(active_op_array)->opcodes[as_token->u.op.opline_num+1]; opline->result_type = IS_TMP_VAR; opline->result.opline_num = get_temporary_variable(CG(active_op_array)); GET_NODE(&key_node, opline->result); zend_do_assign(&dummy, key, &key_node TSRMLS_CC); zend_do_free(&dummy TSRMLS_CC); } //循環的主要邏輯 do_begin_loop(TSRMLS_C); INC_BPC(CG(active_op_array)); } /* }}} */
3. zend_do_foreach_end
void zend_do_foreach_end(const znode *foreach_token, const znode *as_token TSRMLS_DC) /* {{{ */ { zend_op *container_ptr; zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC); //根據zend_do_foreach_begin中記錄的行數信息,設置ZEND_JMP OPCODES,準備跳回foreach的開始處繼續循環 opline->opcode = ZEND_JMP; //根據當前行數,設置循環體下一條opline, 用以跳出循環 opline->op1.opline_num = as_token->u.op.opline_num; SET_UNUSED(opline->op1); SET_UNUSED(opline->op2); CG(active_op_array)->opcodes[foreach_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array)); /* FE_RESET */ CG(active_op_array)->opcodes[as_token->u.op.opline_num].op2.opline_num = get_next_op_number(CG(active_op_array)); /* FE_FETCH */ // 結束循環(處理循環內循環:do_end_loop) do_end_loop(as_token->u.op.opline_num, 1 TSRMLS_CC); //清理臨時變量 zend_stack_top(&CG(foreach_copy_stack), (void **) &container_ptr); generate_free_foreach_copy(container_ptr TSRMLS_CC); zend_stack_del_top(&CG(foreach_copy_stack)); DEC_BPC(CG(active_op_array)); } /* }}} */
除此以外,在zend_do_foreach_cont 和 zend_do_foreach_end之間 會在語法分析階段被填充foreach_satement的語句代碼,基本上這就是foreach的git執行過程
PHP的HashTable(包括數組)是一種鏈表隊列(HashTable結構體中的pListHead和pListTail維護整個哈希表的頭元素指針和最後一個元素的指針),元素按照FIFO的順序進行排列,因此foreach遍歷的順序和入隊的順序有關,同時每一個隊列元素又是一個Bucket雙向鏈表(相同Hash的Bucket以拉鍊法連接在同一個鏈表上,pNext和pLast指針分別指向本槽位所在的鏈表的關係),每一個Bucket中存儲了對應的Hash索引值,這個Hash索引值將數組的鍵直接映射到對應的內存位置,故爲0(1)時間複雜度
<?php $arr[2] = 'littlehann'; $arr[1] = 2007; $arr[0] = 2008; foreach ($arr as $key => $val) { var_dump($val); }
使用foreach並非按照ASCII順序進行遍歷的,而是按照FIFO順序,即遍歷HashTable隊列,因此若是但願按照下標ASC/DEC順序遍歷,最好用for($i=0; $i < count; $i++);;這種結構,明確告訴PHP按照指定下標進行HASH索引取值
Relevant Link:
http://thiniki.sinaapp.com/?p=155 http://www.imsiren.com/archives/250 http://www.cnblogs.com/ohmygirl/p/internal-4.html http://weizhifeng.net/write-php-extension-part2-1.html http://blog.csdn.net/a600423444/article/details/7073854 http://www.laruence.com/2008/11/20/630.html http://www.laruence.com/2009/08/23/1065.html
3. PHP變量實現
0x1: 變量概述
全部的編程語言都要提供一種數據的存儲與檢索機制,PHP也不例外。其它語言大都須要在使用變量以前先定義,而且它的類型也是沒法再次改變的,而PHP卻容許程序猿自由的使用變量而無須提早定義,甚至能夠隨時隨意的對已存在的變量轉換成其它任何PHP支持的數據類型。在程序在運行的時候,PHP還會自動的根據需求轉換變量的類型
從通常編程語言的角度來看,變量具備三個基本特性
1. 名稱: 變量的標示符 1) 變量命名上,PHP繼承了Perl的語法風格,變量以美圓符號開始,後面跟變量名。一個有效的變量名由字母或者下劃線開頭,後面跟上任意數量的字母,數字,或者下劃線 2) PHP同時還支持複合變量,也就是相似$$a的變量,它會進行兩次的解釋。這給PHP帶來了很是靈活的動態特性 3) PHP中組成變量名的字母能夠是英文字母 a-z,A-Z 4) 變量名還能夠是 ASCII 字符從 127 到 255(0x7f-0xff),這給一些惡意腳本進行代碼加密提供的語言基礎 5) 變量名是區分大小寫的 2. 類型: 變量的類型 1) 在不少靜態語言中,變量在定義時就指定了,在程序運行過程當中都不容許進行變動 2) 可是PHP屬於弱類型語言,能夠隨便賦予它任何類型的值 3. 值內容 1) 標示所表明的具體內容
0x2: 變量的類型
前言中提到變量的三個基本特性,其中的有一個特性爲變量的類型,變量都有特定的類型, 如:字符串、數組、對象等等。編程語言的類型系統能夠分爲強類型和弱類型兩種
1. 強類型語言是一旦某個變量被申明爲某個類型的變量,則在程序運行過程當中,該不能將該變量的類型之外的值賦予給它(固然並不徹底如此,這可能會涉及到類型的轉換),C/C++/Java等語言就屬於這類 2. PHP及Ruby,JavaScript等腳本語言屬於弱類型語言: 一個變量能夠表示任意的數據類型。PHP之因此成爲一個簡單而強大的語言,很大一部分的緣由是它擁有弱類型的變量。 可是有些時候這也是一把雙刃劍,使用不當也會帶來一些問題。就像儀器同樣,越是功能強大, 出現錯誤的可能性也就越大
PHP在內核中是經過zval這個結構體來存儲變量的,它的定義在Zend/zend.h文件裏,這就是PHP弱類型的核心
//在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; //表示是否爲引用 }; typedef struct _zval_struct zval; //在Zend/zend_types.h裏定義的: typedef unsigned int zend_uint; typedef unsigned char zend_uchar;
保存變量值的value則是zvalue_value類型,它是一個union,這裏使用聯合體而不是用結構體是出於空間利用率的考慮,由於一個變量同時只能屬於一種類型。 若是使用結構體的話將會沒必要要的浪費空間,而PHP中的全部邏輯都圍繞變量來進行的,這樣的話, 內存浪費將是十分大的。這種作法成本小但收益很是大
Zend/zend.h
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;
在以上實現的基礎上,PHP語言得以實現了8種數據類型,這些數據類型在內核中的分別對應於特定的常量,它們分別是
1. IS_NULL 1) 第一次使用的變量若是沒有初始化過,則會自動的被賦予這個常量,固然咱們也能夠在PHP語言中經過null這個常量來給予變量null類型的值 2) 這個類型的值只有一個,就是NULL,它和0與false是不一樣的 2. IS_BOOL 1) 布爾類型的變量有兩個值,true或者false。在PHP語言中 2) while、if等語句會自動的把表達式的值轉成這個類型的 3. IS_LONG 1) PHP語言中的整型,在內核中是經過所在操做系統的signed long數據類型來表示的。 在最多見的32位操做系統中,它能夠存儲從-2147483648 ~ +2147483647範圍內的任一整數,由於使用了signed long來做爲載體,因此這也就解釋了爲何PHP語言中的整型數據都是帶符號的了 2) 若是PHP語言中的整型變量超出最大值或者最小值,它並不會直接溢出, 而是會被內核轉換成IS_DOUBLE類型的值而後再參與計算。 再者,。 ```c $a=2147483647; $a++; echo $a;//會正確的輸出 2147483648 4. IS_DOUBLE 1) PHP中的浮點數據是經過C語言中的signed double型變量來存儲的,這最終取決與所在操做系統的浮點型實現,計算機是沒法精準的表示浮點數的,而是採用了科學計數法來保存某個精度的浮點數。 用科學計數法,計算機只用8位即可以保存2.225x10^(-308) ~ 1.798x10^308之間的浮點數 2) 用計算機來處理浮點數會存在問題,十進制的0.5轉成二進制是0.1, 0.8轉換後是0.1100110011....。 可是當咱們從二進制轉換回來的時候,每每會發現並不能獲得0.8。 咱們用1除以3這個例子來解釋這個現象:1/3=0.3333333333.....,它是一個無限循環小數, 可是計算機可能只能精確存儲到0.333333,當咱們再乘以三時, 其實計算機計算的數是0.333333*3=0.999999,而不是咱們平時數學中所期盼的1.0. 5. IS_STRING 1) PHP中最經常使用的數據類型: 字符串,在內存中的存儲和C差很少, 就是一塊可以放下這個變量全部字符的內存,而且在這個變量的zval實現裏會保存着指向這塊內存的指針 2) 與C不一樣的是,PHP內核還同時在zval結構裏保存着這個字符串的實際長度, 這個設計使PHP能夠在字符串中嵌入"\0"字符,也使PHP的字符串是二進制安全的,能夠安全的存儲二進制數據 3) 內核只會爲字符串申請它長度+1的內存, 最後一個字節存儲的是'\0'字符,因此在不須要二進制安全操做的時候, 咱們能夠像一般C語言的方式那樣來使用它 6. IS_ARRAY 1) 數組是一個很是特殊的數據類型,它惟一的功能就是彙集別的變量 2) 在C語言中,一個數組只能承載一種類型的數據,而PHP語言中的數組則靈活的多,它能夠承載任意類型的數據,這一切都是HashTable的功勞,每一個HashTable中的元素都有兩部分組成: 索引與值,每一個元素的值都是一個獨立的zval(確切的說應該是指向某個zval的指針),正是由於這種架構設計,使得PHP能夠在數組中以任意方式存儲任意類型的變量 7. IS_OBJECT 1) 和數組同樣,對象也是用來存儲複合數據的,可是與數組不一樣的是,對象還須要保存如下信息: 方法、訪問權限、類常量以及其它的處理邏輯 2) 相對與zend engine V1,V2中的對象實現已經被完全修改,因此咱們PHP擴展開發者若是須要本身的擴展支持面向對象的工做方式, 則應該對PHP5和PHP4分別對待 8. IS_RESOURCE 1) 有一些數據的內容可能沒法直接呈現給PHP用戶的,好比與某臺mysql服務器的連接,或者直接呈現出來也沒有什麼意義 2) 但用戶還須要這類數據,所以PHP中提供了一種名爲Resource(資源)的數據類型
zval結構體裏的type成員的值即是以上某個IS_*常量之一。內核經過檢測變量的這個成員值來知道他是什麼類型的數據並作相應的後續處理
zend頭文件中定義了大量的宏,供咱們檢測、操做變量使用,使用這些宏不但讓咱們的程序更易讀,還具備更好的兼容性,例如檢測變量類型的宏Z_TYPE_P、Z_TYPE、Z_TYPE_PP
咱們結合PHP API函數gettype的內核實現
\php-5.6.17\ext\standard\type.c
/* {{{ proto string gettype(mixed var) Returns the type of the variable */ PHP_FUNCTION(gettype) { ////arg間接指向調用gettype函數時所傳遞的參數。是一個zval**結構 zval **arg; ////這個if的操做主要是讓arg指向參數 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) { return; } //調用Z_TYPE_PP宏來獲取arg指向zval的類型。 //而後是一個switch結構,RETVAL_STRING宏表明這gettype函數返回的字符串類型的值 switch (Z_TYPE_PP(arg)) { case IS_NULL: RETVAL_STRING("NULL", 1); break; case IS_BOOL: RETVAL_STRING("boolean", 1); break; case IS_LONG: RETVAL_STRING("integer", 1); break; case IS_DOUBLE: RETVAL_STRING("double", 1); break; case IS_STRING: RETVAL_STRING("string", 1); break; case IS_ARRAY: RETVAL_STRING("array", 1); break; case IS_OBJECT: RETVAL_STRING("object", 1); /* { char *result; int res_len; res_len = sizeof("object of type ")-1 + Z_OBJCE_P(arg)->name_length; spprintf(&result, 0, "object of type %s", Z_OBJCE_P(arg)->name); RETVAL_STRINGL(result, res_len, 0); } */ break; case IS_RESOURCE: { const char *type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC); if (type_name) { RETVAL_STRING("resource", 1); break; } } default: RETVAL_STRING("unknown type", 1); } } /* }}} */
以上三個宏的定義在Zend/zend_operators.h裏,定義分別是
#define Z_TYPE(zval) (zval).type #define Z_TYPE_P(zval_p) Z_TYPE(*zval_p) #define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)
0x3: 變量的值
PHP是一種若類型語言,在對變量進行讀寫前須要先判斷變量的類型,PHP內核提供了三個基礎宏來方便咱們對變量的值進行操做,這幾個宏一樣以Z_開頭,而且P結尾和PP結尾的同上一節中的宏同樣,分別表明這參數是指針仍是指針的指針。此外,爲了進一步方便咱們的工做,內核中針對具體的數據類型分別定義了相應的宏。如針對IS_BOOL型的BVAL組合(Z_BVAL、Z_BVAL_P、Z_BVAL_PP)和針對IS_DOUBLE的DVAL組合(Z_DVAL、ZDVAL_P、ZDVAL_PP)等等。咱們經過下面這個例子來應用一下這幾個宏
void display_value(zval zv,zval *zv_p,zval **zv_pp) { if( Z_TYPE(zv) == IS_NULL ) { php_printf("類型是 IS_NULL!\n"); } if( Z_TYPE_P(zv_p) == IS_LONG ) { php_printf("類型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p)); } if(Z_TYPE_PP(zv_pp) == IS_DOUBLE ) { php_printf("類型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) ); } }
0x4: 建立PHP變量
PHP對內核變亮表示ZVAL的申請、初始化、賦值操做等都封裝了宏實現,實現了代碼整潔性和升級兼容性
1. MAKE_STD_ZVAL(pzv): 這個宏會用內核的方式來申請一塊內存並將其地址付給pzv,並初始化它的refcount和is_ref兩個屬性,它不但會自動的處理內存不足問題,還會在內存中選個最優的位置來申請 2. ALLOC_INIT_ZVAL(): 這個宏函數也是用來申請內存的, 和MAKE_STD_ZVAL宏惟一的不一樣即是它會將pzv所指的zval的類型設置爲IS_NULL; 3. ZVAL_NULL(pvz); 4. ZVAL_BOOL(pzv, b); 5. ZVAL_TRUE(pzv); 6. ZVAL_FALSE(pzv); 7. ZVAL_LONG(pzv, l); 8. ZVAL_DOUBLE(pzv, d); 9. ZVAL_STRINGL(pzv,str,len,dup); dup參數的意思其實很簡單,它指明瞭該字符串是否須要被複制 1) 值爲 1 將先申請一塊新內存並賦值該字符串,而後把新內存的地址複製給pzv 2) 值爲 0 時則是直接把str的地址賦值給zval 10. ZVAL_STRING(pzv, str, dup); ZVAL_STRING相比於ZVAL_STRINGL的區別在於ZVAL_STRING使用strlen()計算字符串長度,它不是二進制安全的,二進制安全的作法是顯式使用len字段標明該字符串指望的處理長度 11. ZVAL_RESOURCE(pzv, res); PHP中的資源類型的值其實就是一個整數,因此ZVAL_RESOURCE和ZVAL_LONG的工做差很少,只不過它會把zval的類型設置爲 IS_RESOURCE
0x5: 變量的存儲方式
咱們接下來學習一下內核是怎樣來組織用戶在PHP中定義的變量的。用戶在PHP中定義的變量咱們均可以在一個HashTable中找到,當PHP中定義了一個變量,內核會自動的把它的信息儲存到一個用HashTable實現的符號表裏。全局做用域的符號表是在調用擴展的RINIT方法(通常都是MINIT方法裏)前建立的,並在RSHUTDOWN方法執行後自動銷燬(這就是咱們熟悉的$GLOBALS超全局變量)
當用戶在PHP中調用一個函數或者類的方法時,內核會建立一個新的符號表並激活之,這也就是爲何咱們沒法在函數中使用在函數外定義的變量的緣由(由於它們分屬兩個符號表,一個當前做用域的,一個全局做用域的),若是必定要在函數中使用全局變量,則須要使用global顯示聲明
Zend/zend_globals.h
struct _zend_executor_globals { ... //symbol_table元素能夠經過EG宏來訪問,它表明着PHP的全局變量,如$GLOBALS,其實從根本上來說,$GLOBALS是EG(symbol_table)的一層封裝 HashTable symbol_table; //active_symbol_table元素也能夠經過EG(active_symbol_table)的方法來訪問,它表明的是處於當前做用域的變量符號表 HashTable *active_symbol_table; ... };
用一段例子來解釋下上面說的理論
<?php $foo = 'bar'; ?>
上面是一段PHP語言的例子,咱們建立了一個變量,並把它的值設置爲'bar',在之後的代碼中咱們即可以使用$foo變量,在PHP內核中的實現框架以下
1. 建立一個zval結構,並設置其類型 2. 設置值爲'bar' 3. 將其加入當前做用域的符號表,只有這樣用戶才能在PHP裏使用這個變量
具體的代碼爲
{ //聲明一個zval指針,並申請一塊內存 zval *fooval; MAKE_STD_ZVAL(fooval); //經過ZVAL_STRING宏將值設置爲'bar' ZVAL_STRING(fooval, "bar", 1); //將這個zval加入到當前的符號表裏去,並將其label定義成foo,這樣用戶就能夠在代碼裏經過$foo來使用它了 ZEND_SET_SYMBOL( EG(active_symbol_table) , "foo" , fooval); }
0x6: 變量的檢索
用戶在PHP語言裏定義的變量,在PHP內核中能夠經過zend_hash_find()函數來找到當前某個做用域下用戶已經定義好的變量
/* Returns SUCCESS if found and FAILURE if not. The pointer to the * data is returned in pData. The reason is that there's no reason * someone using the hash table might not want to have NULL data */ ZEND_API int zend_hash_find(const HashTable *ht, const char *arKey, uint nKeyLength, void **pData) { ulong h; uint nIndex; Bucket *p; IS_CONSISTENT(ht); h = zend_inline_hash_func(arKey, nKeyLength); nIndex = h & ht->nTableMask; p = ht->arBuckets[nIndex]; while (p != NULL) { if (p->arKey == arKey || ((p->h == h) && (p->nKeyLength == nKeyLength) && !memcmp(p->arKey, arKey, nKeyLength))) { *pData = p->pData; return SUCCESS; } p = p->pNext; } return FAILURE; }
須要明白的是,PHP使用HashTable來保存、讀寫變量,可是內核定義HashTable這個結構,並非單單用來儲存PHP語言裏的變量的,PHP內核裏不少地方都在使用HashTable
0x7: 類型轉換
在純語法層面,PHP是一種若類型語言,能夠在運行時改變當前變量的數據類型
內核中提供了好多函數專門來幫咱們實現類型轉換的功能,這一類函數有一個統一的形式: convertto*()
\php-5.6.17\Zend\zend_operators.c
ZEND_API void _convert_to_cstring(zval *op ZEND_FILE_LINE_DC); ZEND_API void _convert_to_string(zval *op ZEND_FILE_LINE_DC); ZEND_API void convert_to_long(zval *op); ZEND_API void convert_to_double(zval *op); ZEND_API void convert_to_long_base(zval *op, int base); ZEND_API void convert_to_null(zval *op); ZEND_API void convert_to_boolean(zval *op); ZEND_API void convert_to_array(zval *op); ZEND_API void convert_to_object(zval *op); #define convert_to_cstring(op) if ((op)->type != IS_STRING) { _convert_to_cstring((op) ZEND_FILE_LINE_CC); } #define convert_to_string(op) if ((op)->type != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); }
0x8: 預約義變量
PHP腳本在執行的時候用戶全局變量(在用戶空間顯式定義的變量)會保存在一個HashTable數據類型的符號表(symbol_table)中
除此以外,在PHP中有一些比較特殊的全局變量例如: $_GET,$_POST,$_SERVER等變量,咱們並無在程序中定義這些變量,而且這些變量也一樣保存在符號表中,PHP是在腳本運行以前就將這些特殊的變量加入到了符號表中了
1. $GLOBALS的初始化
咱們以cgi模式爲例說明$GLOBALS的初始化,從cgi_main.c文件main函數開始。整個調用順序以下所示
\php-5.6.17\Zend\zend.c zend_register_auto_global("GLOBALS", sizeof("GLOBALS") - 1, 1, php_auto_globals_create_globals TSRMLS_CC); static zend_bool php_auto_globals_create_globals(const char *name, uint name_len TSRMLS_DC) /* {{{ */ { zval *globals; ALLOC_ZVAL(globals); Z_SET_REFCOUNT_P(globals, 1); Z_SET_ISREF_P(globals); Z_TYPE_P(globals) = IS_ARRAY; Z_ARRVAL_P(globals) = &EG(symbol_table); zend_hash_update(&EG(symbol_table), name, name_len + 1, &globals, sizeof(zval *), NULL); return 0; } /* }}} */ [main() -> php_request_startup() -> zend_activate() -> init_executor() ] .. zend_hash_init(&EG(symbol_table), 50, NULL, ZVAL_PTR_DTOR, 0);
php_request_startup函數在PHP的生命週期中屬於請求初始化階段,即每一個請求都會執行這個函數。 所以,對於每一個用戶請求,其用到的這些預約義的全局變量都會不一樣。 $GLOVALS的關鍵點在於zend_hash_update函數的調用,它將變量名爲GLOBALS的變量註冊到EG(symbol_table)中, EG(symbol_table)是一個HashTable的結構,用來存放頂層做用域的變量。 經過這個操做,GLOBAL變量與其它頂層的變量同樣都會註冊到了變量表, 也能夠和其它變量同樣直接訪問了
2. $_GET、$_POST等變量的初始化
$_GET、$_COOKIE、$_SERVER、$_ENV、$_FILES、$_REQUEST這六個變量都是經過以下的調用序列進行初始化
[main() -> php_cli_startup() -> php_module_startup() -> php_startup_auto_globals(TSRMLS_C) ] void php_startup_auto_globals(TSRMLS_D) { zend_register_auto_global(ZEND_STRL("_GET"), 0, php_auto_globals_create_get TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_POST"), 0, php_auto_globals_create_post TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_COOKIE"), 0, php_auto_globals_create_cookie TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_SERVER"), PG(auto_globals_jit), php_auto_globals_create_server TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_ENV"), PG(auto_globals_jit), php_auto_globals_create_env TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_REQUEST"), PG(auto_globals_jit), php_auto_globals_create_request TSRMLS_CC); zend_register_auto_global(ZEND_STRL("_FILES"), 0, php_auto_globals_create_files TSRMLS_CC); }
以$_POST爲例說明整個初始化的過程
static zend_bool php_auto_globals_create_post(const char *name, uint name_len TSRMLS_DC) { zval *vars; if ( PG(variables_order) && (strchr(PG(variables_order),'P') || strchr(PG(variables_order),'p')) && SG(request_info).request_method && !strcasecmp(SG(request_info).request_method, "POST")) { //按照PG(variables_order)指定的順序(在php.ini中指定),經過調用sapi_module.treat_data處理數據 sapi_module.treat_data(PARSE_POST, NULL, NULL TSRMLS_CC); vars = PG(http_globals)[TRACK_VARS_POST]; } else { ALLOC_ZVAL(vars); array_init(vars); INIT_PZVAL(vars); if (PG(http_globals)[TRACK_VARS_POST]) { zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_POST]); } PG(http_globals)[TRACK_VARS_POST] = vars; } //將POST變量添加到全局符號表中,因此咱們能在$GLOBALS中直接訪問到POST參數 zend_hash_update(&EG(symbol_table), name, name_len + 1, &vars, sizeof(zval *), NULL); Z_ADDREF_P(vars); return 0; /* don't rearm */ }
0x9: 預約義變量的獲取
在某個局部函數中使用相似於$GLOBALS變量這樣的預約義變量,若是在此函數中有改變的它們的值的話,這些變量在其它局部函數調用時會發現也會同步變化,從PHP中間代碼的執行來看,這些變量是存儲在一個集中的地方: 即EG(symbol_table)
在模塊初始化時,$GLOBALS在zend_startup函數中經過調用zend_register_auto_global將GLOBALS註冊爲預約義變量,$_GET、$_POST等在php_startup_auto_globals函數中經過zend_register_auto_global將_GET、_POST等註冊爲預約義變量
在經過$獲取變量時,PHP內核都會經過這些變量名區分是否爲全局變量(ZEND_FETCH_GLOBAL),其調用的判斷函數爲zend_is_auto_global,這個過程是在生成中間代碼過程當中實現的。若是是ZEND_FETCH_GLOBAL或ZEND_FETCH_GLOBAL_LOCK(global語句後的效果),則在獲取獲取變量表時(zend_get_target_symbol_table),直接返回EG(symbol_table)。則這些變量的全部操做都會在全局變量表進行
0x10: 靜態變量的實現
一般意義上靜態變量是靜態分配的,他們的生命週期和程序的生命週期同樣,只有在程序退出時才結束期生命週期,這和局部變量相反,有的語言中全局變量也是靜態分配的。例如PHP和Javascript中的全局變量
靜態變量能夠分爲
1. 靜態全局變量: PHP中的全局變量也能夠理解爲靜態全局變量,由於除非明確unset釋放,在程序運行過程當中始終存在 2. 靜態局部變量: 在函數內定義的靜態變量,函數在執行時對變量的操做會保持到下一次函數被調用 3. 靜態成員變量: 這是在類中定義的靜態變量,和實例變量相對應,靜態成員變量能夠在全部實例中共享
下面經過一個具體的實例來看PHP在用戶態語法層面和內核態層面分別是怎麼處理靜態變量的
<?php function t() { static $i = 0; $i++; echo $i, ' '; } t(); t(); t(); ?>
static是PHP的關鍵字,咱們須要從詞法分析,語法分析,中間代碼生成到執行中間代碼這幾個部分探討整個實現過程
1. 詞法分析
Zend/zend_language_scanner.l
<ST_IN_SCRIPTING>"static" { return T_STATIC; }
PHP詞法分析向語法分析器傳入T_STATIC這個詞素
2. 語法分析
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
\php-5.6.17\Zend\zend_compile.c
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); } //在解釋成中間代碼時,靜態變量是存放在CG(active_op_array)->static_variables中的 if (!CG(active_op_array)->static_variables) { /* 初始化此時的靜態變量存放位置 */ if (CG(active_op_array)->scope) { CG(active_op_array)->scope->ce_flags |= ZEND_HAS_STATIC_IN_METHODS; } // 將新的靜態變量放進來 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, Z_STRVAL(varname->u.constant), Z_STRLEN(varname->u.constant)+1, &tmp, sizeof(zval *), NULL); if (varname->op_type == IS_CONST) { if (Z_TYPE(varname->u.constant) != IS_STRING) { convert_to_string(&varname->u.constant); } } opline = get_next_op(CG(active_op_array) TSRMLS_CC); opline->opcode = (fetch_type == ZEND_FETCH_LEXICAL) ? ZEND_FETCH_R : ZEND_FETCH_W; /* the default mode must be Write, since fetch_simple_variable() is used to define function arguments */ opline->result_type = IS_VAR; opline->result.var = get_temporary_variable(CG(active_op_array)); SET_NODE(opline->op1, varname); if (opline->op1_type == IS_CONST) { CALCULATE_LITERAL_HASH(opline->op1.constant); } SET_UNUSED(opline->op2); opline->extended_value = ZEND_FETCH_STATIC; GET_NODE(&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) { znode dummy; zend_do_begin_variable_parse(TSRMLS_C); zend_do_assign(&dummy, &lval, &result TSRMLS_CC); zend_do_free(&dummy TSRMLS_CC); } else { // 賦值操做中間代碼生成 zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC); } CG(active_op_array)->opcodes[CG(active_op_array)->last-1].result_type |= EXT_TYPE_UNUSED; } /* }}} */
由此再次能夠看出,PHP的語法解析過程,就是從Token詞素向opcode的生成過程,對於PHP來講,詞法產生式就是對應於zend的opcode產生函數
4. 執行中間代碼
opcode的編譯階段完成後就開始opcode的執行了。 在Zend/zend_vm_opcodes.h文件中包含全部opcode的宏定義,Zend爲每一個函數(準確的說是zend_op_array)分配了一個私有的符號表來保存該函數的靜態變量,這就是函數內靜態變量生命週期的底層原理
0x11: 變量類型提示
http://www.nowamagic.net/librarys/veda/detail/1409
0x12: 變量的生命週期
http://www.nowamagic.net/librarys/veda/detail/1414
0x13: 變量賦值與銷燬
http://www.nowamagic.net/librarys/veda/detail/1415
0x14: 變量做用域
http://www.nowamagic.net/librarys/veda/detail/1420
Relevant Link:
http://udn.yyuap.com/doc/wiki/project/extending-embedding-php/2.html http://www.walu.cc/phpbook/2.md http://www.nowamagic.net/librarys/veda/detail/1326 http://www.nowamagic.net/librarys/veda/detail/1327 http://www.nowamagic.net/librarys/veda/detail/1386 http://www.nowamagic.net/librarys/veda/detail/1387 http://www.nowamagic.net/librarys/veda/detail/1388 http://www.nowamagic.net/librarys/veda/detail/1389 http://www.php-internals.com/book/?p=chapt03/03-03-pre-defined-variable http://www.nowamagic.net/librarys/veda/detail/1390 http://www.nowamagic.net/librarys/veda/detail/1402
4. PHP常量實現
值得注意的是,PHP內核中變量(_zval_struct)和常量(_zend_constant)是兩個獨立的結構表示,它們在更底層都複用了zval以實現值表示
0x1: 數據結構
常量顧名思義是一個常態的量值。它與值只綁定一次,它的做用在於有肋於增長程序的可讀性和可靠性。 在PHP中,常量的名字是一個簡單值的標識符,在腳本執行期間該值不能改變。 和變量同樣,常量默認爲大小寫敏感,可是按照咱們的習慣常量標識符老是大寫的。 常量名和其它任何 PHP 標籤遵循一樣的命名規則。合法的常量名以字母或下劃線開始,後面跟着任何字母,數字或下劃線
常量是在變量的zval結構的基礎上添加了一額外的元素。以下所示爲PHP中常量的內部結構
\php-5.6.17\Zend\zend_constants.h
typedef struct _zend_constant { zval value; /* zval結構,PHP內部變量的存儲結構 */ int flags; /* 常量的標記如 CONST_PERSISTENT | CONST_CS */ char *name; /* 常量名稱 */ uint name_len; int module_number; /* 模塊號 */ } zend_constant;
0x2: define定義常量
define是PHP的內置函數
\php-5.6.17\Zend\zend_builtin_functions.c
/* {{{ proto bool define(string constant_name, mixed value, boolean case_insensitive=false) Define a new constant */ 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; } //將傳遞的參數傳遞給新建的zend_constant結構 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; } } /* }}} */
Relevant Link:
http://www.nowamagic.net/librarys/veda/detail/1368
Copyright (c) 2016 Little5ann All rights reserved