【php7扩展开发六】zval的操作

本文详细解析PHP7中zval的生成与操作,涵盖不同类型zval的设置、获取值及类型,以及数组和字符串操作。同时,深入探讨了引用计数机制在变量赋值、数组操作、函数调用和成员属性中的应用。

生成各类型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 dumped
	Z_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))

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值