PHP 引用
通常,当你向函数传递参数时,是通过值来传递的,这意味着被调用的函数不能修改它。一般来说,在 PHP 扩展中可以修改通过值传递的参数,但很可能会导致内存错误和崩溃。如果需要修改函数的参数(如 sort() 函数),则必须通过引用传递。
通过引用传递是 php 引用的主要用途,但在其他数据结构(如数组的元素)中也随处可见。
在内部,引用由 zval 表示,IS_REFERENCE 为类型,zend_reference 结构指针为值。与所有引用计数类型一样,它继承自 zend_refcounted 结构,该结构定义了第一个 64 位字的格式。结构的其余部分是另一个嵌入的 zval。

检查或检索引用 zvals 的字段的 C 宏很少(后缀为 _P
的宏也会指向 zvals):
-
Z_ISREF(zv) – 检查该值是否为 PHP 引用(类型为 IS_REFERENCE)。
-
Z_REF(zv) – 返回依赖的 zend_reference 结构(类型必须是 IS_REFERENCE)。
-
Z_REFVAL(zv) – 返回指向引用值(zval)的指针。
此外,还有一些用于构建引用和去引用的宏:
-
ZVAL_REF(zv, ref) - 通过 IS_REFERENCE 类型和 zend_reference 指针初始化 zval。
-
ZVAL_NEW_EMPTY_REF(zv) - 通过 IS_REFERENCE 类型和一个新的 zend_reference 结构来初始化 zval。Z_REFVAL_P(zv) 需要在调用后进行初始化。
-
ZVAL_NEW_REF(zv, value) - 使用 IS_REFERENCE 类型初始化 zval,并使用给定的值初始化一个新的 zend_reference 结构。
-
ZVAL_MAKE_REF_EX(zv, refcount) - 使用给定的引用计数器将 "zv" 转换为 PHP 引用。
-
ZVAL_DEREF(zv) - 如果 "zv" 是一个引用,则取消引用(指向引用值的指针被赋值给 "zv")。
在我们的扩展实例中使用 php 引用
让我们尝试向 test_scale() 函数传递一个引用。
$ php -r '$a = 5; var_dump(test_scale([&$a], 2));'
Warning: test_scale(): unexpected argument type in Command line code on line 1
NULL
不支持引用。
要解决这个问题,我们只需添加取消引用即可。
static int do_scale(zval *return_value, zval *x, zend_long factor)
{
ZVAL_DEREF(x);
if (Z_TYPE_P(x) == IS_LONG) {
RETVAL_LONG(Z_LVAL_P(x) * factor);
现在一切都很好:
$ php -r '$a = 5; var_dump(test_scale([&$a], 2));'
array(1) {
[0]=>
int(10)
}
我们还要将 test_scale() 转换为函数 test_scale_ref(),它不会返回任何值,但会通过引用接收参数,并将传递的值就地相乘。
static int do_scale_ref(zval *x, zend_long factor)
{
ZVAL_DEREF(x);
if (Z_TYPE_P(x) == IS_LONG) {
Z_LVAL_P(x) *= factor;
} else if (Z_TYPE_P(x) == IS_DOUBLE) {
Z_DVAL_P(x) *= factor;
} else if (Z_TYPE_P(x) == IS_STRING) {
size_t len = Z_STRLEN_P(x);
char *p;
ZVAL_STR(x, zend_string_safe_realloc(Z_STR_P(x), len, factor, 0, 0));
p = Z_STRVAL_P(x) + len;
while (--factor > 0) {
memcpy(p, Z_STRVAL_P(x), len);
p += len;
}
*p = '\000';
} else if (Z_TYPE_P(x) == IS_ARRAY) {
zval *val;
ZEND_HASH_FOREACH_VAL(Z_ARR_P(x), val) {
if (do_scale_ref(val, factor) != SUCCESS) {
return FAILURE;
}
} ZEND_HASH_FOREACH_END();
} else {
php_error_docref(NULL, E_WARNING, "unexpected argument type");
return FAILURE;
}
return SUCCESS;
}
PHP_FUNCTION(test_scale_ref)
{
zval * x;
zend_long factor = TEST_G(scale); // default value
ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_ZVAL(x)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(factor)
ZEND_PARSE_PARAMETERS_END();
do_scale_ref(x, factor);
}
ZEND_BEGIN_ARG_INFO(arginfo_test_scale_ref, 1)
ZEND_ARG_INFO(1, x) // pass by reference
ZEND_ARG_INFO(0, factor)
ZEND_END_ARG_INFO()
static const zend_function_entry test_functions[] = {
PHP_FE(test_test1, arginfo_test_test1)
PHP_FE(test_test2, arginfo_test_test2)
PHP_FE(test_scale_ref, arginfo_test_scale_ref)
PHP_FE_END
};
测试:
$ php -r '$x=5; test_scale_ref($x, 2); var_dump($x);'
int(10)
$ php -r '$x=5.0; test_scale_ref($x, 2);
var_dump($x);'
float(10)
$ php -r '$x="5"; test_scale_ref($x, 2); var_dump($x);'
string(2) "55"
$ php -r '$x=[[5]]; test_scale_ref($x, 2); var_dump($x);'
array(1) {
[0]=> array(1) {
[0]=>
int(10)
}
}
一切看起来都是正确的,但在某些情况下,我们的函数会表现不正确。有关如何通过使用 "写入时复制" 避免出现问题的详细信息,请参阅下一节。