迟早你会遇到把一个 zval 容器的内容赋给另外一个 zval 容器的情况。不过可别想当然,这事说起来容易做起来可有点难度。因为 zval 容器不但包含了类型信息,而且还有对 Zend 内部数据的一些引用。比如,数组以及对象等依据其大小大都或多或少包含了一些哈希表结构。而我们在将一个 zval 赋给另外一个 zval 时,通常都没有复制这些哈希表本身,复制的只是这些哈希表的引用而已。

为了能够正确复制这些复杂类型的数据,我们可以使用“拷贝构造函数(copy constructor)”来完成这项工作。拷贝构造函数在某些为了可以复制复杂类型数据而支持操作符重载的语言中有着代表性的应用。如果你在这种语言中定义了一个对象,那你就可能想为其重载(Overloading)一下“=”操作符,这个操作符通常用于将右值(操作符右边表达式的值)赋给左值(操作符左边表达式的值)。

“重载”就意味着将给予这个操作符另外一种不同的含义,它通常会把这个操作符跟某个函数调用关联起来。当这个操作符作用在一个对象上时,与之关联的函数就将会被调用,同时该操作符的左值和右值也会作为该函数的参数一并传入。这样,这个函数就可以完成“=”操作符想要完成的事情(一般是某些额外数据的复制)。

这些“额外数据的复制”对 PHP 的 zval 容器来说也是很有必要的。对于数组来说,“额外数据的复制”就是指另外再重建和复制那些与该数组有关的哈希表(因为当初我们复制 zval 时复制的仅仅是这些哈希表的指针)。而对字符串来说,“额外数据的复制”就意味着我们必须重新为字符串值去申请空间。如此类推。

Zend Engine 会调用一个名为 zval_copy_ctor()(在以前的 PHP 版本中这个函数叫做 pval_copy_constructor() )的函数来完成这项工作。

下面这个示例为我们展示了这样一个函数:它接收一个复杂类型的参数,在对其进行一定的修改后把它作为结果返回给 PHP:

zval *parameter;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", ¶meter) == FAILURE) {
    return;
}

// 在这对参数做一定的修改
……

// 返回修改后的容器
*return_value = *parameter;
zval_copy_ctor(return_value);

函数的头一部分没什么可说的,只是一段很平常的接收参数的代码而已。不过在对这个参数进行了某些修改后就变得有趣起来了:先是把 parameter 容器值赋给了(预先定义好的)return_value 容器,然后为了能够真正复制这个容器,我们便调用了拷贝构造函数。这个拷贝构造函数能够直接处理它的参数,处理成功则返回 SUCCESS,否则返回 FAILURE

在这个例子当中如果你忘了调用这个拷贝构造函数,那么 parameterreturn_value 就会分别指向同一个 Zend 内部数据,也就是说返回值 return_value 非法指向了一个数据结构。当你修改了参数 parameter 时这个函数的返回值就可能会受到影响。因此为了创建一个独立的拷贝,我们必须调用这个函数。

在 Zend API 中还有一个与拷贝构造函数相对应的拷贝析构函数:zval_dtor(),它做的工作正好与拷贝构造函数相反。