生成各類型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)?? ?zval設置為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的值及類型
zval的類型通過 Z_TYPE(zval) 、 Z_TYPE_P(zval*) 兩個宏獲取,這個值取的就是zval.u1.v.type ,但是設置時不要只修改這個type,而是要設置typeinfo,因為zval還有其它的標識需要設置,比如是否使用引用計數、是否可被垃圾回收、是否可被復制等等。
書寫一個類似gettype()來取得變量的類型的hello_typeof():
PHP_FUNCTION(hello_typeof)
{zval *userval = NULL;if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &userval) == FAILURE) {RETURN_NULL();}switch (Z_TYPE_P(userval)) {case IS_NULL:RETVAL_STRING("NULL");break;case IS_TRUE:RETVAL_STRING("true");break;case IS_FALSE:RETVAL_STRING("false");break;case IS_LONG:RETVAL_STRING("integer");break;case IS_DOUBLE:RETVAL_STRING("double");break;case IS_STRING:RETVAL_STRING("string");break;case IS_ARRAY:RETVAL_STRING("array");break;case IS_OBJECT:RETVAL_STRING("object");break;case IS_RESOURCE:RETVAL_STRING("resource");break;default:RETVAL_STRING("unknown type");}
}
獲取不同類型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*
數組操作
數組作為運載其他變量的變量。內部實現上使用了眾所周知的 HashTable .要創建將被返回PPHP的數組,最簡單的方法:
向數字索引的數組增加指定類型的值
$arr = array();?? ?=> array_init(arr);?? ?初始化一個新數組
$arr[] = NULL;?? ? add_next_index_null(arr);?? ??
$arr[] = 42;?? ? add_next_index_long(arr, 42);?? ?
$arr[] = true;?? ? add_next_index_bool(arr, 1);?? ?
$arr[] = 3.14;?? ? add_next_index_double(arr, 3.14);?? ?
$arr[] = 'foo'; ?add_next_index_string(arr, "foo", 1);?? ?
$arr[] = $myvar; add_next_index_zval(arr, myvar);?? ?
向數組中指定的數字索引增加指定類型的值
$arr[0] = NULL;????????add_index_null(arr, 0);?????
$arr[1]= 42;????????????add_index_long(arr, 1, 42);????
$arr[2] = true;????????add_index_bool(arr, 2, 1);????
$arr[3] = 3.14;????????add_index_double(arr, 3, 3.14);????
$arr[4] = 'foo';????add_index_string(arr, 4, "foo", 1);????
$arr[5] = $myvar;????add_index_zval(arr, 5, myvar);????
?
向關聯索引的數組增加指定類型的值
$arr['abc'] = NULL;????????add_assoc_null(arr, "abc");????
$arr['def'] = 711;????????add_assoc_long(arr, "def", 711);?????
$arr['ghi'] = true;????????add_assoc_bool(arr, "ghi", 1);????
$arr['jkl'] = 1.44;????????add_assoc_double(arr, "jkl", 1.44);????
$arr['mno'] = 'baz';????add_assoc_string(arr, "mno", "baz", 1);????
$arr['pqr'] = $myvar;????add_assoc_zval(arr, "pqr", myvar);
字符串操作
//創建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)))
...
引用計數
在擴展中操作與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));
}
那么在哪些情況下需要考慮設置引用計數呢?
(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_REFCO
UNTED) != 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))
?
總結
以上是生活随笔為你收集整理的【php7扩展开发六】zval的操作的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。