你也许会认为扩展中定义的函数应该直接通过return关键字来返回一个值,比如由你自己来生成一个zval并返回,就像下面这样:
ZEND_FUNCTION(sample_long_wrong)
{
    zval *retval;
    MAKE_STD_ZVAL(retval);
    ZVAL_LONG(retval, 42);
    return retval;
}
但是,上面的写法是无效的!与其让扩展开发员每次都初始化一个zval并return之,zend引擎早就准备好了一个更好的方法。它在每个zif函数声明里加了一个zval*类型的形参,名为return_value,专门来解决返回值这个问题。在前面我们已经知道了ZEND_FUNCTION宏展开后是void name(INTERNAL_FUNCTION_PARAMETERS)的形式,现在是我们展开代表参数声明的INTERNAL_FUNCTION_PARAMETERS宏的时候了。
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
PHP_FUNCTION(sample_long)
{
    RETURN_LONG(42);
    //#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
    php_printf("I will never be reached.\n"); //这一行代码永远不会被执行。
}
下面,我们给出目前所有的RETVAL_***宏和RETURN_***宏,供大家查阅使用。
````c
//这些宏都定义在Zend/zend_API.h文件里
#define RETVAL_RESOURCE(l)                ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b)                    ZVAL_BOOL(return_value, b)
#define RETVAL_NULL()                     ZVAL_NULL(return_value)
#define RETVAL_LONG(l)                     ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d)                 ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate)         ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate)     ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING()             ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor)        ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE                      ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE                       ZVAL_BOOL(return_value, 1)
#define RETURN_RESOURCE(l)                 { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b)                     { RETVAL_BOOL(b); return; }
#define RETURN_NULL()                     { RETVAL_NULL(); return;}
#define RETURN_LONG(l)                     { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d)                 { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate)     { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING()             { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor)        { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE                      { RETVAL_FALSE; return; }
#define RETURN_TRUE                       { RETVAL_TRUE; return; }
````
其实,除了这些标量类型,还有很多php语言中的复合类型我们需要在函数中返回,如数组和对象,我们可以通过RETVAL_ZVAL与RETURN_ZVAL来操作它们,有关它们的详细介绍我们将在后续章节中叙述。
### 不返回值可以么?
其实,zend internal function的形参中还有一个比较常用的名为return_value_used的参数,它是干嘛使的呢?它用来标志这个函数的返回值在用户端有没有用到。看下面的代码:
````php
在上面的代码中,$b其实是$a的一个引用,当最后一行代码执行后,$a和$b都开始寻找‘bar’这个字符串对应的zval,让我们以内核的角度重新观察这一切:
#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0)
ZEND_FUNCTION(return_by_ref)
{
    zval **a_ptr;
    zval *a;
    //检查全局作用域中是否有$a这个变量,如果没有则添加一个
    //在内核中真的是可以胡作非为啊,:-)
    if(zend_hash_find(&EG(symbol_table) , "a",sizeof("a"),(void **)&a_ptr ) == SUCCESS )
    {
        a = *a_ptr;
    }
    else
    {
        ALLOC_INIT_ZVAL(a);
        zend_hash_add(&EG(symbol_table), "a", sizeof("a"), &a,sizeof(zval*), NULL);
    }
    //废弃return_value,使用return_value_ptr来接替它的工作
    zval_ptr_dtor(return_value_ptr);
    if( !a->is_ref__gc && a->refcount__gc > 1 )
    {
        zval *tmp;
        MAKE_STD_ZVAL(tmp);
        *tmp = *a;
        zval_copy_ctor(tmp);
        tmp->is_ref__gc = 0;
        tmp->refcount__gc = 1;
        zend_hash_update(&EG(symbol_table), "a", sizeof("a"), &tmp,sizeof(zval*), NULL);
        a = tmp;
    }
    a->is_ref__gc = 1;
    a->refcount__gc++;
    *return_value_ptr = a;
}
#endif /* PHP >= 5.1.0 */
````
return_value_ptr是定义zend internal function时的另外一个重要参数,他是一个zval**类型的指针,并且指向函数的返回值。我们调用zval_ptr_dtor()函数后,默认的return_value便被废弃了。这里的$a变量如果是与某个非引用形式的变量共用一个zval的话,便要进行分离。
不幸的是,如果你编译上面的代码,使用的时候便会得到一个段错误。为了使它能够正常的工作,需要在源文件中加一些东西:
````c
#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0)
    ZEND_BEGIN_ARG_INFO_EX(return_by_ref_arginfo, 0, 1, 0)
    ZEND_END_ARG_INFO ()
#endif /* PHP >= 5.1.0 */
然后使用下面的代码来申明我们的定义的函数:
#if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0)
    ZEND_FE(return_by_ref, return_by_ref_arginfo)
#endif /* PHP >= 5.1.0 */
````
arginfo是一种特殊的结构体,用来提前向内核告知此函数具有的一些特定的性质,如本例,其将告诉内核本函数需要引用形式的返回值,所以内核不再通过return_value来获取执行结果,而是通过return_value_ptr。如果没有arginfo,那内核会预先把return_value_ptr置为NULL,当我们对其调用zval_ptr_dtor()函数时便会使程序崩溃。
这一些代码都包含在了一个宏里面,只有在php版本大于等于5.1的时候才会被启用。如果没有这些if、endif,那我们的程序将无法在php4下通过编译,在php5.0上也会激活一些无法预测的错误。
    - zone(zqx10104#163.com)于2011-10-20提供了一个Bug,:-)
 
links
- 6 函数返回值
 
- 6.2 引用与函数的执行结果