写时复制

下面的代码不应该修改变量 $y,但却修改了。

php -r '$x=$y=[5]; test_scale_ref($x, 2); var_dump($x, $y);'
array(1) {
    [0]=>
    int(10)
}
array(1) {
    [0]=>
    int(10)
}

这是一个错误。出现这种情况的原因是,我们在就地更新某个值时,没有考虑其引用计数器。如果引用计数器大于 1,同一值就会被其他地方引用。我们必须将其 "分离"(或执行写时复制)。

这可以通过一些宏来实现:

  • SEPARATE_STRING(zv) - 如果 PHP 字符串的引用计数器大于 1,将执行写入时复制。

  • SEPARATE_ARRAY(zv) - 如果 PHP 数组的引用计数器大于 1,将执行写入时复制。

在我们的例子中,我们只需要 "分离" 数组,因为 zend_string_safe_realloc() 会考虑引用计数,并在写入时执行复制。解决方法很简单:

} else if (Z_TYPE_P(x) == IS_ARRAY) {
    zval *val;

    SEPARATE_ARRAY(x);
    ZEND_HASH_FOREACH_VAL(Z_ARR_P(x), val) {
        if (do_scale_ref(val, factor) != SUCCESS) {

下面的代码修正后的执行结果。

php -r '$x=$y=[5]; test_scale_ref($x, 2); var_dump($x, $y);'
array(1) {
    [0]=>
    int(10)
}
array(1) {
    [0]=>
    int(5)
}