62-形参return value

原创 2016年04月27日 13:21:33

62-形参return value

PHP语言中函数的返回值是通过return来完成了,就像下面的程序:

<?php
function sample_long() {
  return 42;
}
$bar = sample_long();
?>

C语言也一样使用return关键字:

int sample_long(void) {
    return 42;
}
int main(void) {
    int bar = sample_long();
    return 1;
}

那我们在扩展中编写的PHP函数如何把返回值回馈给用户端的函数调用者呢?看好,这里指的是回馈给,而不是单单的return~

你也许会认为扩展中定义的函数应该直接通过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
  • int ht
  • zval *return_value,我们在函数内部修改这个指针,函数执行完成后,内核将把这个指针指向的zval返回给用户端的函数调用者。
  • zval **return_value_ptr,
  • zval *this_ptr,如果此函数是一个类的方法,那么这个指针的含义和PHP语言中$this变量差不多。
  • int return_value_used,代表用户端在调用此函数时有没有使用到它的返回值。

下面让我们先试验一个非常简单的例子,我先给出PHP语言中的实现,然后给出我们在扩展中用C语言完成相同功能的代码。

<?php
function sample_long()
{
    return 42;
}
/*
    这个函数非常简单.
    $a = sample_long();
    那此时$a的值便是42了,这个我们大家肯定都明白。
*/
?>

下面是我们在编写扩展时的实现。

ZEND_FUNCTION(sample_long)
{
    ZVAL_LONG(return_value, 42);
    return;
}

需要注意的是,ZEND_FUNCTION本身并没有通过return关键字返回任何有价值的东西,它只不过是在运行时修改了return_value指针所指向的变量的值而已,而内核则会把return_value指向的变量作为用户端调用此函数后的得到的返回值。回想一下,ZVAL_LONG()宏是对一类操作的封装,展开后应该就是下面这样:

Z_TYPE_P(return_value) = IS_LONG;
Z_LVAL_P(return_value) = 42;

//更彻底的讲,应该是这样的:
return_value->type = IS_LONG;
return_value->value.lval = 42;

我们千万不要自己去修改return_value的is_ref__gc和refcount__gc属性,这两个属性的值会由PHP内核自动管理。

现在我们把它加到我们在第五章得到的那个扩展框架里,并把这个函数名称注册到函数入口数组里,就像下面这样:

static zend_function_entry walu_functions[] = {
    ZEND_FE(walu_hello,        NULL)
    PHP_FE(sample_long, NULL)
    { NULL, NULL, NULL }
};

现在我们编译我们的扩展,便可以在用户端通过调用sample_long函数来得到一个整型的返回值了:

<?php var_dump(sample_long());?>

与return_value有关的宏

return_value如此重要,内核肯定早已经为它准备了大量的宏,来简化我们的操作,提高程序的质量。 在前几章我们接触的宏大多都是以ZVAL_开头的,而接下来我们要介绍的宏的名字是:RETVAL。 再回到上面的那个例子,我们用RETVAL来重写一下:

PHP_FUNCTION(sample_long)
{
    RETVAL_LONG(42);
    //展开后相当与ZVAL_LONG(return_value, 42);
    return;
}

大多数情况下,我们在处理玩return_value后所做的便是用return语句结束我们的函数执行,帮人帮到底,送佛送到西,为了减少我们的工作量,内核中还提供了RETURN_*系列宏来为我们自动补上return;如:

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_***宏,供大家查阅使用。

//这些宏都定义在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 
function sample_array_range() {
    $ret = array();
    for($i = 0; $i < 1000; $i++) {
        $ret[] = $i;
    }
    return $ret;
}
sample_array_range();
?>

sample_array_range()仅仅是执行了一下而已,并没有使用到函数的返回值。函数的返回值$ret初始化并返回给调用者后根本就没有发挥作用,却白白浪费了很多内存来存储它的1000个元素。虽然这个例子有点极端,但是却提醒了我们,如果返回值没有被用到,我有没有办法在函数中提前知晓并进行一些有利于性能的操作呢?

这个想法在PHP脚本语言里简直就是异想天开,肯定是无法实现的。但是如果我们所处的环境是内核,即zif,便可以轻松实现这个愿望了,而我们所需要做的便是充分利用return_value_used这个参数:

ZEND_FUNCTION(sample_array_range)
{
    if (return_value_used) {
        int i;

        //把返回值初始化成一个PHP语言中的数组
        array_init(return_value);
        for(i = 0; i < 1000; i++)
        {
            //向retrun_value里不断的添加新元素,值为i
            add_next_index_long(return_value, i);
        }
        return;
    }
    else
    {
        //抛出一个E_NOTICE级错误
        php_error_docref(NULL TSRMLS_CC, E_NOTICE,"猫了个咪的,我就知道你没用我的劳动成果!");
        RETURN_NULL();
    }
}

以引用的形式返回值

你肯定已经在手册中看到过有关将函数的返回值以引用的形式的返回的技术了。但是因为某些历史原因,在为扩展编写函数时候如果想然返回值以引用的形式返回时一定要慎之又慎,因为在php5.1之前,根本就没法真正的实现这个功能,look一下下面的代码:

<?php
//关于PHP语言中引用形式返回值的详述,请参考PHP手册。
$a = 'china';

function &return_by_ref()
{
    global $a;
    return $a;
}

$b = &return_by_ref();
$b = "php";
echo $a;
//此时程序输出php
?>

在上面的代码中,ba的一个引用,当最后一行代码执行后,ab都开始寻找‘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的话,便要进行分离。

不幸的是,如果你编译上面的代码,使用的时候便会得到一个段错误。为了使它能够正常的工作,需要在源文件中加一些东西:

#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上也会激活一些无法预测的错误。

相关文章推荐

JS——函数形参匹配与return语句

这是类别《小知识点》的第一篇,特此说明,此类别记录一些计算机、编程语言等相关的杂散知识点,有的是笔试时遇到的题目,虽然不一定有参考价值(因为有的题目完全是为了笔试而设,谁会写一些开发中根本不符规范,可...

24_方法_方法的本质_形参_实参_return语句

方法 设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。 我们设计方法的时候,最好保持方法的原子性,就是一个方法只完成1个功能,这样利于我们后期的扩展。 方法的要点: Ja...

函数的定义声明 用法 形参实参 return关键字

day08 函数 什么是函数 :函数就是一段特殊功能的代码 函数 定义 的语法函数必须写在main函数的外面 格式eg: 数据类型 函数名称(参数列表) { ...

SignalR消息发送后的返回值(Return value in SignalR)

Return Values On question during my lecture about SignalR was, how return values can be achieved....

springMVC返回json格式数据报错:No converter found for return value of type: class java.util.HashMap

博主刚在写上一篇文章: SpringMVC异步上传多文件 的时候返回json格式对象,一直报错:No converter found for return value of type: class j...
  • wqh8522
  • wqh8522
  • 2017年05月28日 18:08
  • 1539

【已解决】java.lang.IllegalArgumentException: No converter found for return value of type: class java.uti

最近在做自己的网站,有一个模块是点赞,为了减小不必要的数据传递,考虑用ajax+json与后台(spring mvc)交互实现。 run的时候,后台controller能够接收到前台传来的数据,也能...

The Name Return Value Optimization

I received the following mail the other day in response to my writing that the Visual C++ compiler h...

Named Return Value Optimization

已知下列函数定义:X bar() { X xx; // process xx ... return xx; }  你可能会问bar()的返回值如何从局部对象xx中拷贝过来?实现...

Let function return a correct value you want

/* return.c by vinco at 2011-08-04 Ubuntu9.10 CC/GCC */ #include #include #include int return_v...

Android Studio各种坑爹之:finished with non-zero exit value 1和Return code 1 for dex process.

android { compileSdkVersion 25 buildToolsVersion "25.0.0" //添加如下代码即可解决 dexOptions { ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:62-形参return value
举报原因:
原因补充:

(最多只允许输入30个字)