php7-internal 7.7 zval的操作
擴展中經常會用到各種類型的zval,PHP提供了很多宏用于不同類型zval的操作,盡管我們也可以自己操作zval,但這并不是一個好習慣,因為zval有很多其它用途的標識,如果自己去管理這些值將是非常繁瑣的一件事,所以我們應該使用PHP提供的這些宏來操作用到的zval。
7.7.1 新生成各類型zval
PHP7將變量的引用計數轉移到了具體的value上,所以zval更多的是作為統一的傳輸格式,很多情況下只是臨時性使用,比如函數調用時的傳參,最終需要的數據是zval攜帶的zend_value,函數從zval取得zend_value后就不再關心zval了,這種就可以直接在棧上分配zval。分配完zval后需要將其設置為我們需要的類型以及設置其zend_value,PHP中定義的ZVAL_XXX()系列宏就是用來干這個的,這些宏第一個參數z均為要設置的zval的指針,后面為要設置的zend_value。
- ZVAL_UNDEF(z): 表示zval被銷毀
- ZVAL_NULL(z): 設置為NULL
- ZVAL_FALSE(z): 設置為false
- ZVAL_TRUE(z): 設置為true
- ZVAL_BOOL(z, b): 設置為布爾型,b為IS_TRUE、IS_FALSE,與上面兩個等價
- ZVAL_LONG(z, l): 設置為整形,l類型為zend_long,如:zval z; ZVAL_LONG(&z, 88);
- ZVAL_DOUBLE(z, d): 設置為浮點型,d類型為double
- ZVAL_STR(z, s): 設置字符串,將z的value設置為s,s類型為zend_string*,不會增加s的refcount,支持interned strings
- ZVAL_NEW_STR(z, s): 同ZVAL_STR(z, s),s為普通字符串,不支持interned strings
- ZVAL_STR_COPY(z, s): 將s拷貝到z的value,s類型為zend_string*,同ZVAL_STR(z, s),這里會增加s的refcount
- ZVAL_ARR(z, a): 設置為數組,a類型為zend_array*
- ZVAL_NEW_ARR(z): 新分配一個數組,主動分配一個zend_array
- ZVAL_NEW_PERSISTENT_ARR(z): 創建持久化數組,通過malloc分配,需要手動釋放
- ZVAL_OBJ(z, o): 設置為對象,o類型為zend_object*
- ZVAL_RES(z, r): 設置為資源,r類型為zend_resource*
- ZVAL_NEW_RES(z, h, p, t): 新創建一個資源,h為資源handle,t為type,p為資源ptr指向結構
- ZVAL_REF(z, r): 設置為引用,r類型為zend_reference*
- ZVAL_NEW_EMPTY_REF(z): 新創建一個空引用,沒有設置具體引用的value
- ZVAL_NEW_REF(z, r): 新創建一個引用,r為引用的值,類型為zval*
- ...
7.7.2 獲取zval的值及類型
zval的類型通過Z_TYPE(zval)、Z_TYPE_P(zval*)兩個宏獲取,這個值取的就是zval.u1.v.type,但是設置時不要只修改這個type,而是要設置typeinfo,因為zval還有其它的標識需要設置,比如是否使用引用計數、是否可被垃圾回收、是否可被復制等等。
內核提供了Z_XXX(zval)、Z_XXX_P(zval*)系列的宏用于獲取不同類型zval的value。
- Z_LVAL(zval)、Z_LVAL_P(zval_p): 返回zend_long
- Z_DVAL(zval)、Z_DVAL_P(zval_p): 返回double
- Z_STR(zval)、Z_STR_P(zval_p): 返回zend_string*
- Z_STRVAL(zval)、Z_STRVAL_P(zval_p): 返回char*,即:zend_string->val
- Z_STRLEN(zval)、Z_STRLEN_P(zval_p): 獲取字符串長度
- Z_STRHASH(zval)、Z_STRHASH_P(zval_p): 獲取字符串的哈希值
- Z_ARR(zval)、Z_ARR_P(zval_p)、Z_ARRVAL(zval)、Z_ARRVAL_P(zval_p): 返回zend_array*
- Z_OBJ(zval)、Z_OBJ_P(zval_p): 返回zend_object*
- Z_OBJ_HT(zval)、Z_OBJ_HT_P(zval_p): 返回對象的zend_object_handlers,即zend_object->handlers
- Z_OBJ_HANDLER(zval, hf)、Z_OBJ_HANDLER_P(zv_p, hf): 獲取對象各操作的handler指針,hf為write_property、read_property等,注意:這個宏取到的為只讀,不要試圖修改這個值(如:Z_OBJ_HANDLER(obj, write_property) = xxx;),因為對象的handlers成員前加了const修飾符
- Z_OBJCE(zval)、Z_OBJCE_P(zval_p): 返回對象的zend_class_entry*
- Z_OBJPROP(zval)、Z_OBJPROP_P(zval_p): 獲取對象的成員數組
- Z_RES(zval)、Z_RES_P(zval_p): 返回zend_resource*
- Z_RES_HANDLE(zval)、Z_RES_HANDLE_P(zval_p): 返回資源handle
- Z_RES_TYPE(zval)、Z_RES_TYPE_P(zval_p): 返回資源type
- Z_RES_VAL(zval)、Z_RES_VAL_P(zval_p): 返回資源ptr
- Z_REF(zval)、Z_REF_P(zval_p): 返回zend_reference*
- Z_REFVAL(zval)、Z_REFVAL_P(zval_p): 返回引用的zval*
除了這些與PHP變量類型相關的宏之外,還有一些內核自己使用類型的宏:
//獲取indirect的zval,指向另一個zval #define Z_INDIRECT(zval) (zval).value.zv #define Z_INDIRECT_P(zval_p) Z_INDIRECT(*(zval_p))#define Z_CE(zval) (zval).value.ce #define Z_CE_P(zval_p) Z_CE(*(zval_p))#define Z_FUNC(zval) (zval).value.func #define Z_FUNC_P(zval_p) Z_FUNC(*(zval_p))#define Z_PTR(zval) (zval).value.ptr #define Z_PTR_P(zval_p) Z_PTR(*(zval_p))7.7.3 類型轉換
//將原類型轉為特定類型,會更改原來的值 ZEND_API void ZEND_FASTCALL convert_to_long(zval *op); ZEND_API void ZEND_FASTCALL convert_to_double(zval *op); ZEND_API void ZEND_FASTCALL convert_to_long_base(zval *op, int base); ZEND_API void ZEND_FASTCALL convert_to_null(zval *op); ZEND_API void ZEND_FASTCALL convert_to_boolean(zval *op); ZEND_API void ZEND_FASTCALL convert_to_array(zval *op); ZEND_API void ZEND_FASTCALL convert_to_object(zval *op);#define convert_to_cstring(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_cstring((op) ZEND_FILE_LINE_CC); } #define convert_to_string(op) if (Z_TYPE_P(op) != IS_STRING) { _convert_to_string((op) ZEND_FILE_LINE_CC); }//獲取格式化為long的值,不會更改原來的值,op類型為zval*,返回值為zend_long #define zval_get_long(op) _zval_get_long((op)) //獲取格式化為double的值,返回值double #define zval_get_double(op) _zval_get_double((op)) //獲取格式化為string的值,返回值zend_string * #define zval_get_string(op) _zval_get_string((op))//字符串轉整形 ZEND_API int ZEND_FASTCALL zend_atoi(const char *str, int str_len); ZEND_API zend_long ZEND_FASTCALL zend_atol(const char *str, int str_len);//判斷是否為true #define zval_is_true(op) \zend_is_true(op)7.7.4 引用計數
在擴展中操作與PHP用戶空間相關的變量時需要考慮是否需要對其引用計數進行加減,比如下面這個例子:
function test($arr){ return $arr; } $a = array(1,2); $b = test($a);如果把函數test()用內部函數實現,這個函數接受了一個PHP用戶空間傳入的數組參數,然后又返回并賦值給了PHP用戶空間的另外一個變量,這個時候就需要增加傳入數組的refcount,因為這個數組由PHP用戶空間分配,函數調用前refcount=1,傳到內部函數時相當于賦值給了函數的參數,因此refcount增加了1變為2,這次增加在函數執行完釋放參數時會減掉,等返回并賦值給$b后此時共有兩個變量指向這個數組,所以內部函數需要增加refcount,增加的引用是給返回值的。test()翻譯成內部函數:
PHP_FUNCTION(test) { zval *arr;if(zend_parse_parameters(ZEND_NUM_ARGS(), "a", &arr) == FAILURE){RETURN_FALSE;}//如果注釋掉下面這句將導致core dumpedZ_TRY_ADDREF_P(arr);RETURN_ARR(Z_ARR_P(arr)); }那么在哪些情況下需要考慮設置引用計數呢?一個關鍵條件是:操作的是與PHP用戶空間相關的變量,包括對用戶空間變量的修改、賦值,要明確的一點是引用計數是用來解決多個變量指向同一個value問題的,所以在PHP中來回傳遞zval的時候就需要考慮下是不是要修改引用計數,下面總結下PHP中常見的會對引用計數進行操作的情況:
- (1)變量賦值: 變量賦值是最常見的情況,一個用到引用計數的變量類型在初始賦值時其refcount=1,如果后面把此變量又賦值給了其他變量那么就會相應的增加其引用計數
- (2)數組操作: 如果把一個變量插入數組中那么就需要增加這個變量的引用計數,如果要刪除一個數組元素則要相應的減少其引用
- (3)函數調用: 傳參實際可以當做普通的變量賦值,將調用空間的變量賦值給被調函數空間的變量,函數返回時會銷毀函數空間的變量,這時又會減掉傳參的引用,這兩個過程由內核完成,不需要擴展自己處理
- (4)成員屬性: 當把一個變量賦值給對象的成員屬性時需要增加引用計數
PHP中定義了以下宏用于引用計數的操作:
//獲取引用數:pz類型為zval* #define Z_REFCOUNT_P(pz) zval_refcount_p(pz) //設置引用數 #define Z_SET_REFCOUNT_P(pz, rc) zval_set_refcount_p(pz, rc) //增加引用 #define Z_ADDREF_P(pz) zval_addref_p(pz) //減少引用 #define Z_DELREF_P(pz) zval_delref_p(pz)#define Z_REFCOUNT(z) Z_REFCOUNT_P(&(z)) #define Z_SET_REFCOUNT(z, rc) Z_SET_REFCOUNT_P(&(z), rc) #define Z_ADDREF(z) Z_ADDREF_P(&(z)) #define Z_DELREF(z) Z_DELREF_P(&(z))//只對使用了引用計數的變量類型增加引用,建議使用這個 #define Z_TRY_ADDREF_P(pz) do { \if (Z_REFCOUNTED_P((pz))) { \Z_ADDREF_P((pz)); \} \ } while (0)#define Z_TRY_DELREF_P(pz) do { \if (Z_REFCOUNTED_P((pz))) { \Z_DELREF_P((pz)); \} \ } while (0)#define Z_TRY_ADDREF(z) Z_TRY_ADDREF_P(&(z)) #define Z_TRY_DELREF(z) Z_TRY_DELREF_P(&(z))這些宏操作類型都是zval或zval*,如果需要操作具體value的引用計數可以使用以下宏:
//直接獲取zend_value的引用,可以直接通過這個宏修改value的refcount #define GC_REFCOUNT(p) (p)->gc.refcount另外還有幾個常用的宏:
//判斷zval是否用到引用計數機制 #define Z_REFCOUNTED(zval) ((Z_TYPE_FLAGS(zval) & IS_TYPE_REFCOUNTED) != 0) #define Z_REFCOUNTED_P(zval_p) Z_REFCOUNTED(*(zval_p))//根據zval獲取value的zend_refcounted頭部 #define Z_COUNTED(zval) (zval).value.counted #define Z_COUNTED_P(zval_p) Z_COUNTED(*(zval_p))7.7.5 字符串操作
PHP中字符串(即:zend_string)操作相關的宏及函數:
//創建zend_string zend_string *zend_string_init(const char *str, size_t len, int persistent);//字符串復制,只增加引用 zend_string *zend_string_copy(zend_string *s);//字符串拷貝,硬拷貝 zend_string *zend_string_dup(zend_string *s, int persistent);//將字符串按len大小重新分配,會減少s的refcount,返回新的字符串 zend_string *zend_string_realloc(zend_string *s, size_t len, int persistent);//延長字符串,與zend_string_realloc()類似,不同的是len不能小于s的長度 zend_string *zend_string_extend(zend_string *s, size_t len, int persistent);//截斷字符串,與zend_string_realloc()類似,不同的是len不能大于s的長度 zend_string *zend_string_truncate(zend_string *s, size_t len, int persistent);//獲取字符串refcount uint32_t zend_string_refcount(const zend_string *s);//增加字符串refcount uint32_t zend_string_addref(zend_string *s);//減少字符串refcount uint32_t zend_string_delref(zend_string *s);//釋放字符串,減少refcount,為0時銷毀 void zend_string_release(zend_string *s);//銷毀字符串,不管引用計數是否為0 void zend_string_free(zend_string *s);//比較兩個字符串是否相等,區分大小寫,memcmp() zend_bool zend_string_equals(zend_string *s1, zend_string *s2);//比較兩個字符串是否相等,不區分大小寫 #define zend_string_equals_ci(s1, s2) \(ZSTR_LEN(s1) == ZSTR_LEN(s2) && !zend_binary_strcasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2)))//其它宏,zstr類型為zend_string* #define ZSTR_VAL(zstr) (zstr)->val //獲取字符串 #define ZSTR_LEN(zstr) (zstr)->len //獲取字符串長度 #define ZSTR_H(zstr) (zstr)->h //獲取字符串哈希值 #define ZSTR_HASH(zstr) zend_string_hash_val(zstr) //計算字符串哈希值除了上面這些,還有很多字符串大小轉換、字符串比較的API定義在zend_operators.h中,這里不再列舉。
7.7.6 數組操作
7.7.6.1 創建數組
創建一個新的HashTable分為兩步:首先是分配zend_array內存,這個可以通過ZVAL_NEW_ARR()宏分配,也可以自己直接分配;然后初始化數組,通過zend_hash_init()宏完成,如果不進行初始化數組將無法使用。
#define zend_hash_init(ht, nSize, pHashFunction, pDestructor, persistent) \_zend_hash_init((ht), (nSize), (pDestructor), (persistent) ZEND_FILE_LINE_CC)- ht: 數組地址HashTable*,如果內部使用可以直接通過emalloc分配
- nSize: 初始化大小,只是參考值,這個值會被對齊到2^n,最小為8
- pHashFunction: 無用,設置為NULL即可
- pDestructor: 刪除或更新數組元素時會調用這個函數對操作的元素進行處理,比如將一個字符串插入數組,字符串的refcount增加,刪除時不是簡單的將元素的Bucket刪除就可以了,還需要對其refcount進行處理,這個函數就是進行清理工作的
- persistent: 是否持久化
示例:
zval array; uint32_t size;ZVAL_NEW_ARR(&array); zend_hash_init(Z_ARRVAL(array), size, NULL, ZVAL_PTR_DTOR, 0);7.7.6.2 插入、更新元素
數組元素的插入、更新主要有三種情況:key為zend_string、key為普通字符串、key為數值索引,相關的宏及函數:
// 1) key為zend_string//插入或更新元素,會增加key的refcount #define zend_hash_update(ht, key, pData) \_zend_hash_update(ht, key, pData ZEND_FILE_LINE_CC)//插入或更新元素,當Bucket類型為indirect時,將pData更新至indirect的值,而不是更新Bucket #define zend_hash_update_ind(ht, key, pData) \_zend_hash_update_ind(ht, key, pData ZEND_FILE_LINE_CC)//添加元素,與zend_hash_update()類似,不同的地方在于如果元素已經存在則不會更新 #define zend_hash_add(ht, key, pData) \_zend_hash_add(ht, key, pData ZEND_FILE_LINE_CC)//直接插入元素,不管key存在與否,如果存在也不覆蓋原來元素,而是當做哈希沖突處理,所有會出現一個數組中key相同的情況,慎用!!! #define zend_hash_add_new(ht, key, pData) \_zend_hash_add_new(ht, key, pData ZEND_FILE_LINE_CC)// 2) key為普通字符串:char*//與上面幾個對應,這里的key為普通字符串,會自動生成zend_string的key #define zend_hash_str_update(ht, key, len, pData) \_zend_hash_str_update(ht, key, len, pData ZEND_FILE_LINE_CC) #define zend_hash_str_update_ind(ht, key, len, pData) \_zend_hash_str_update_ind(ht, key, len, pData ZEND_FILE_LINE_CC) #define zend_hash_str_add(ht, key, len, pData) \_zend_hash_str_add(ht, key, len, pData ZEND_FILE_LINE_CC) #define zend_hash_str_add_new(ht, key, len, pData) \_zend_hash_str_add_new(ht, key, len, pData ZEND_FILE_LINE_CC)// 3) key為數值索引//插入元素,h為數值 #define zend_hash_index_add(ht, h, pData) \_zend_hash_index_add(ht, h, pData ZEND_FILE_LINE_CC)//與zend_hash_add_new()類似 #define zend_hash_index_add_new(ht, h, pData) \_zend_hash_index_add_new(ht, h, pData ZEND_FILE_LINE_CC)//更新第h個元素 #define zend_hash_index_update(ht, h, pData) \_zend_hash_index_update(ht, h, pData ZEND_FILE_LINE_CC)//使用自動索引值 #define zend_hash_next_index_insert(ht, pData) \_zend_hash_next_index_insert(ht, pData ZEND_FILE_LINE_CC)#define zend_hash_next_index_insert_new(ht, pData) \_zend_hash_next_index_insert_new(ht, pData ZEND_FILE_LINE_CC)7.7.6.3 查找元素
//根據zend_string key查找數組元素 ZEND_API zval* ZEND_FASTCALL zend_hash_find(const HashTable *ht, zend_string *key);//根據普通字符串key查找元素 ZEND_API zval* ZEND_FASTCALL zend_hash_str_find(const HashTable *ht, const char *key, size_t len);//獲取數值索引元素 ZEND_API zval* ZEND_FASTCALL zend_hash_index_find(const HashTable *ht, zend_ulong h);//判斷元素是否存在 ZEND_API zend_bool ZEND_FASTCALL zend_hash_exists(const HashTable *ht, zend_string *key); ZEND_API zend_bool ZEND_FASTCALL zend_hash_str_exists(const HashTable *ht, const char *str, size_t len); ZEND_API zend_bool ZEND_FASTCALL zend_hash_index_exists(const HashTable *ht, zend_ulong h);//獲取數組元素數 #define zend_hash_num_elements(ht) \(ht)->nNumOfElements //與zend_hash_num_elements()類似,會有一些特殊處理 ZEND_API uint32_t zend_array_count(HashTable *ht);7.7.6.4 刪除元素
//刪除key ZEND_API int ZEND_FASTCALL zend_hash_del(HashTable *ht, zend_string *key);//與zend_hash_del()類似,不同地方是如果元素類型為indirect則同時銷毀indirect的值 ZEND_API int ZEND_FASTCALL zend_hash_del_ind(HashTable *ht, zend_string *key); ZEND_API int ZEND_FASTCALL zend_hash_str_del(HashTable *ht, const char *key, size_t len); ZEND_API int ZEND_FASTCALL zend_hash_str_del_ind(HashTable *ht, const char *key, size_t len); ZEND_API int ZEND_FASTCALL zend_hash_index_del(HashTable *ht, zend_ulong h); ZEND_API void ZEND_FASTCALL zend_hash_del_bucket(HashTable *ht, Bucket *p);7.7.6.5 遍歷
數組遍歷類似foreach的用法,在擴展中可以通過如下的方式遍歷:
zval *val; ZEND_HASH_FOREACH_VAL(ht, val) {... } ZEND_HASH_FOREACH_END();遍歷過程中會把數組元素賦值給val,除了上面這個宏還有很多其他用于遍歷的宏,這里列幾個比較常用的:
//遍歷獲取所有的數值索引 #define ZEND_HASH_FOREACH_NUM_KEY(ht, _h) \ZEND_HASH_FOREACH(ht, 0); \_h = _p->h;//遍歷獲取所有的key #define ZEND_HASH_FOREACH_STR_KEY(ht, _key) \ZEND_HASH_FOREACH(ht, 0); \_key = _p->key;//上面兩個的聚合 #define ZEND_HASH_FOREACH_KEY(ht, _h, _key) \ZEND_HASH_FOREACH(ht, 0); \_h = _p->h; \_key = _p->key;//遍歷獲取數值索引key及value #define ZEND_HASH_FOREACH_NUM_KEY_VAL(ht, _h, _val) \ZEND_HASH_FOREACH(ht, 0); \_h = _p->h; \_val = _z;//遍歷獲取key及value #define ZEND_HASH_FOREACH_STR_KEY_VAL(ht, _key, _val) \ZEND_HASH_FOREACH(ht, 0); \_key = _p->key; \_val = _z;#define ZEND_HASH_FOREACH_KEY_VAL(ht, _h, _key, _val) \ZEND_HASH_FOREACH(ht, 0); \_h = _p->h; \_key = _p->key; \_val = _z;7.7.6.6 其它操作
//合并兩個數組,將source合并到target,overwrite為元素沖突時是否覆蓋 #define zend_hash_merge(target, source, pCopyConstructor, overwrite) \_zend_hash_merge(target, source, pCopyConstructor, overwrite ZEND_FILE_LINE_CC)//導出數組 ZEND_API HashTable* ZEND_FASTCALL zend_array_dup(HashTable *source); #define zend_hash_sort(ht, compare_func, renumber) \zend_hash_sort_ex(ht, zend_sort, compare_func, renumber)數組排序,compare_func為typedef int (*compare_func_t)(const void *, const void ),需要自己定義比較函數,參數類型為Bucket,renumber表示是否更改鍵值,如果為1則會在排序后重新生成各元素的h。PHP中的sort()、rsort()、ksort()等都是基于這個函數實現的。
7.7.6.7 銷毀數組
ZEND_API void ZEND_FASTCALL zend_array_destroy(HashTable *ht);來源:https://github.com/pangudashu/php7-internal/blob/master/7/var.md
總結
以上是生活随笔為你收集整理的php7-internal 7.7 zval的操作的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 间接引语(说一说间接引语的简介)
- 下一篇: 憾生(说一说憾生的简介)