### 7.7.4 引用计数 在扩展中操作与PHP用户空间相关的变量时需要考虑是否需要对其引用计数进行加减,比如下面这个例子: ```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()翻译成内部函数: ```c 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)); } ``` 那么在哪些情况下需要考虑设置引用计数呢?一个关键条件是:操作的是与PHP用户空间相关的变量,包括对用户空间变量的修改、赋值,要明确的一点是引用计数是用来解决多个变量指向同一个value问题的,所以在PHP中来回传递zval的时候就需要考虑下是不是要修改引用计数,下面总结下PHP中常见的会对引用计数进行操作的情况: * __(1)变量赋值:__ 变量赋值是最常见的情况,一个用到引用计数的变量类型在初始赋值时其refcount=1,如果后面把此变量又赋值给了其他变量那么就会相应的增加其引用计数 * __(2)数组操作:__ 如果把一个变量插入数组中那么就需要增加这个变量的引用计数,如果要删除一个数组元素则要相应的减少其引用 * __(3)函数调用:__ 传参实际可以当做普通的变量赋值,将调用空间的变量赋值给被调函数空间的变量,函数返回时会销毁函数空间的变量,这时又会减掉传参的引用,这两个过程由内核完成,不需要扩展自己处理 * __(4)成员属性:__ 当把一个变量赋值给对象的成员属性时需要增加引用计数 PHP中定义了以下宏用于引用计数的操作: ```c //获取引用数: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的引用计数可以使用以下宏: ```c //直接获取zend_value的引用,可以直接通过这个宏修改value的refcount #define GC_REFCOUNT(p) (p)->gc.refcount ``` 另外还有几个常用的宏: ```c //判断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)) ```