深深明PHP内核(十二)函数-函数的定义、传参及返回值

初稿链接:http://www.orlion.ga/344/

同、函数的定义

  用户函数的概念从function 关键字开始,如下

function foo($var) {    echo $var;
}

  1、词法分析

  在Zend/zend_language_scanner.l中我们找到如下所出示之代码:

<ST_IN_SCRIPTING>"function" {    return T_FUNCTION;
}

  它所表示的意义是function将会见生成T_FUNCTION标记。在得这符号后,我们开语法分析。

  2、语法分析

  在Zend/zend_language_parser.y文件被找到函数的宣示过程标记如下:

PHP 1

function:
    T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }
;
 
is_reference:        /* empty */ { $$.op_type = ZEND_RETURN_VAL; }    |   '&'         { $$.op_type = ZEND_RETURN_REF; }
;
 
unticked_function_declaration_statement:
        function is_reference T_STRING {
zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }            '(' parameter_list ')' '{' inner_statement_list '}' {
                zend_do_end_function_declaration(&$1 TSRMLS_CC); }
;

PHP 2

    关注点在function is_reference
T_STRING,表示function关键字,是否引用,函数叫

  T_FUNCTION标记只是用来定位函数的宣示,表示马上是一个函数,而再度多的干活是与此函数相关的事物,包括参数,返回值。

  3、生成中间代码

  语法解析后,我们看到所实施编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到该促成如下:

PHP 3

 zend_do_begin_function_declaration(znode ** is_method,  return_reference, znode *fn_flags_znode TSRMLS_DC) 
    function_token->u.op_array ==== &=
 
    „!*opline =->opcode =->op1.op_type =&opline->->op2.op_type =->op2.u.constant.type =->op2.u.constant.value.str.val =->op2.u.constant.value.str.len =->op2.u.constant, ->extended_value =-
>->op1.u.constant.value.str.len, & **) &

PHP 4

  生成的代码为ZEND_DECLARE_FUNCTION,根据这当中的代码和操作数对应的op_type。我们得找到中间代码的履函数为ZEND_DECLARE_FUNCTION_SPEC_HANDLER。

    在转中间代码的时候,可以看已经统一了函数号称全吧小写,表示函数的称谓不是区  分大小写的。

  为证这个实现,我们看无异段子代码

PHP 5

function T() {
    echo 1;
}
 
function t() {
    echo 2;
}

PHP 6

  执行代码会报错Fatal error: Cannot redeclare t() (previously declared
in …)

  表示于PHP来说T和t是同一个函数称,校验函数称是否还,这个历程是于哪进行的也罢?

  4、执行中间代码

  在Zend/zend_vm_execute.h文件中找到ZEND_DECLARE_FUNCTION中间代码对应的实施函数:ZEND_DECLARE_FUNCTION_SPEC_HANDLER。此函数只调用了函数do_bind_function。其调用代码为:

do_bind_function(EX(opline), EG(function_table), 0);

  在斯函数中将EX(opline)所对的函数添加到EG(function_table)中,并判是否已存在一样名字的函数,如果在则报错,EG(function_table)用来存放执行过程遭到总体的函数信息,相当给函数的注册表。它的组织是一个HashTable,所以当do_bind_function函数中补充加新的函数使用的是HashTable的操作函数zend_hash_add

 

仲、函数的参数

  函数的定义只是一个以函数名注册及函数列表的进程。

  1、用户从定义函数的参数

  我们掌握对函数的参数检查是由此zend_do_receive_arg函数来实现之,在这函数中对此参数的要紧代码如下:

PHP 7

CG(active_op_array)->arg_info = erealloc(CG(active_op_array)->arg_info,        sizeof(zend_arg_info)*(CG(active_op_array)->num_args));
cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];
cur_arg_info->name = estrndup(varname->u.constant.value.str.val,
        varname->u.constant.value.str.len);
cur_arg_info->name_len = varname->u.constant.value.str.len;
cur_arg_info->array_type_hint = 0;
cur_arg_info->allow_null = 1;
cur_arg_info->pass_by_reference = pass_by_reference;
cur_arg_info->class_name = NULL;
cur_arg_info->class_name_len = 0;

PHP 8

  整个参数的传递是由此让中间代码的arg_info字段执行赋值操作完成。关键点是在arg_info字段,arg_info字段的结构如下:

PHP 9

typedef struct _zend_arg_info {    const char *name;   /*参数的名称*/
    zend_uint name_len;     /*参数名称的长度*/
    const char *class_name; /* 类名*/
     zend_uint class_name_len;   /*类名长度*/
    zend_bool array_type_hint;  /*数组类型提示*/
    zend_bool allow_null;   /*是否允许为NULLͺ*/
    zend_bool pass_by_reference;    /*是否引用传递*/
    zend_bool return_reference; 
    int required_num_args;  
} zend_arg_info;

PHP 10

  参数的价值传递和参数传递的区别是由此pass_by_reference参数在变化中间代码时落实之。

  对于参数的个数,中间代码中带有的arg_nums字段在历次执行**zend_do_receive_argxx时还见面加1.之类代码:

CG(active_op_array)->num_args++;

  并且当前参数的目录为ŒCG(active_op_array)->num_args-1.如下代码:

cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];

  以上之辨析是指向函数定义时的参数设置,这些参数是永恒的。而在实际上编写程序时可能我们会用到可换参数。此时我们见面为此到函数func_num_args和func_get_args。它们是为其中函数存在。于是当Zend\zend_builtin_functions.c文件被找到这点儿个函数的实现。我们率先来看func_num_args函数的贯彻,其代码如下:

PHP 11

/* {{{ proto int func_num_args(void)
   Get the number of arguments that were passed to the function */ZEND_FUNCTION(func_num_args)
{
    zend_execute_data *ex = EG(current_execute_data)->prev_execute_data; 
    if (ex && ex->function_state.arguments) {
        RETURN_LONG((long)(zend_uintptr_t)*(ex->function_state.arguments));
    } else {
        zend_error(E_WARNING,"func_num_args():  Called from the global scope - no function context");
        RETURN_LONG(-1);
    }
}/* }}} */

PHP 12

  在存在ex->function_state.arguments的情事下,及函数调用时,返回ex->function_state.arguments转化后的价,否则显示错误并回-1。这里最关键的一点凡EG(current_execute_data)。这个变量存放的是眼前推行顺序还是函数的数目,此时咱们得取前一个实践顺序的数码,为什么吗?因为这个函数的调用是于上函数后实施的。函数的连锁数据等都以头里实施过程被,于是调用的是:

zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;

 

  2、内部函数的参数

  因常见的count函数为条例,其参数处理局部的代码如下:

PHP 13

/* {{{ proto int count(mixed var [, int mode])
   Count the number of elements in a variable (usually an array) */PHP_FUNCTION(count)
{
    zval *array;    long mode = COUNT_NORMAL; 
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l",         &array, &mode) == FAILURE) {        return;
    }
    ... //省略}

PHP 14

  这里连了区区个操作:一个是抱参数的个数,一个凡是分析参数列表。

  (1)取参数的个数

  取参数的个数是透过ZEND_NUM_ARGS()宏来实现之,其定义如下:

#define ZEND_NUM_ARGS()     (ht)

  ht是在Zend/zend.h文件中定义之宏INTERNAL_FUNCTION_PARAMETERS中的ht,如下

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value,zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

  (2)解析参数列表

  PHP内部函数在条分缕析参数时采取的凡zend_parse_parametersPHP。它好大大简化参数的收处理工作,虽然它们以拍卖可更换参数时还闹接触死亡。

  其声明如下:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, 
...)
  • 先是个参数num_args表明表示怀念要接过的参数个数,我们经常利用ZEND_NUM_ARGS()来代表对传播的参数“有略要稍微”

  • 亚只参数应该是宏TSRMLS_CC。

  • 老三独参数type_spec是一个字符串,用来指定我们所企盼接收的依次参数的档次,有硌类似于printf中指定输出格式的不得了格式化字符串。

  • 余下的参数就是我们就此来接收PHP参数值的变量的指针。

  zend_parse_parameters()在分析参数的还要户尽可能的转移参数类型,这样即使足以确保我们总是能够获得所想之类别的变量

 

  3、函数的返回值

  PHP中函数都发返回值,没return返回null

  (1)return语句

  从Zend/zend_language_parser.y文件中得确认其变动中间代码调用的是zend_do_return函数。

PHP 15

void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) /* {{{ */{
    zend_op *opline;    int start_op_number, end_op_number; if (do_end_vparse) {        if (CG(active_op_array)->return_reference                && !zend_is_function_or_method_call(expr)) {
            zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC);/* 处理返回引用 */
        } else {
            zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC);/* 处理常规变量返回 */
        }
    }
 
   ...// 省略,取其他中间代码操作 
    opline->opcode = ZEND_RETURN; 
    if (expr) {
        opline->op1 = *expr; 
        if (do_end_vparse && zend_is_function_or_method_call(expr)) {
            opline->extended_value = ZEND_RETURNS_FUNCTION;
        }
    } else {
        opline->op1.op_type = IS_CONST;
        INIT_ZVAL(opline->op1.u.constant);
    }
 
    SET_UNUSED(opline->op2);
}/* }}} */

PHP 16

  生成中间代码为ZEND_RETURN。第一只操作数的种在返回值为可用的表达式时,其类别也表达式的操作类型,否则路为IS_CONST。这当后续计算执行中间代码函数时有用到。根据操作数的两样,ZEND_RETURN中间代码会尽ZEND_RETURN_SPEC_CONST_HANDLER,ZEND_RETURN_SPEC_TMP_HANDLER或ZEND_RETURN_SPEC_TMP_HANDLER。这三只函数的施行流程基本类似,包括对一部分谬误的处理。这里我们为ZEND_RETURN_SPEC_CONST_HANDLER也例说明函数返回值的尽过程:

PHP 17

static int ZEND_FASTCALL  
ZEND_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op *opline = EX(opline);
    zval *retval_ptr;
    zval **retval_ptr_ptr; 
 
    if (EG(active_op_array)->return_reference == ZEND_RETURN_REF) { 
        //  ǓǔŷsÁ\ɁƶMļ@ɗÁĻļ
        if (IS_CONST == IS_CONST || IS_CONST == IS_TMP_VAR) {   
            /* Not supposed to happen, but we'll allow it */
            zend_error(E_NOTICE, "Only variable references \
                should be returned by reference");
            goto return_by_value;
        }
 
        retval_ptr_ptr = NULL;  //  ǓǔŔ
 
        if (IS_CONST == IS_VAR && !retval_ptr_ptr) {
            zend_error_noreturn(E_ERROR, "Cannot return string offsets by reference");        } if (IS_CONST == IS_VAR && !Z_ISREF_PP(retval_ptr_ptr)) {            if (opline->extended_value == ZEND_RETURNS_FUNCTION &&
                EX_T(opline->op1.u.var).var.fcall_returned_reference) {
            } else if (EX_T(opline->op1.u.var).var.ptr_ptr ==
                    &EX_T(opline->op1.u.var).var.ptr) {                if (IS_CONST == IS_VAR && !0) {                      /* undo the effect of get_zval_ptr_ptr() */
                    PZVAL_LOCK(*retval_ptr_ptr);
                }
                zend_error(E_NOTICE, "Only variable references \
                 should be returned by reference");
                goto return_by_value;
            }
        } 
        if (EG(return_value_ptr_ptr)) { //  Ǔǔŷs
            SEPARATE_ZVAL_TO_MAKE_IS_REF(retval_ptr_ptr);   //  is_ref__gcőęŒ1
            Z_ADDREF_PP(retval_ptr_ptr);    //  refcount__gcŒď×1 
            (*EG(return_value_ptr_ptr)) = (*retval_ptr_ptr);
        }
    } else {
return_by_value:
 
        retval_ptr = &opline->op1.u.constant; 
        if (!EG(return_value_ptr_ptr)) {            if (IS_CONST == IS_TMP_VAR) {
 
            }
        } else if (!0) { /* Not a temp var */
            if (IS_CONST == IS_CONST ||
                EG(active_op_array)->return_reference == ZEND_RETURN_REF ||
                (PZVAL_IS_REF(retval_ptr) && Z_REFCOUNT_P(retval_ptr) > 0)) {
                zval *ret;
 
                ALLOC_ZVAL(ret);
                INIT_PZVAL_COPY(ret, retval_ptr);   //  Ł™ͿʍǓǔŔ                 zval_copy_ctor(ret);                *EG(return_value_ptr_ptr) = ret;
            } else {                *EG(return_value_ptr_ptr) = retval_ptr; //  ħ6ɶŔ                Z_ADDREF_P(retval_ptr);
            }
        } else {
            zval *ret;
 
            ALLOC_ZVAL(ret);
            INIT_PZVAL_COPY(ret, retval_ptr);    //  Ł™ͿʍǓǔŔ 
            *EG(return_value_ptr_ptr) = ret;    
        }
    } 
    return zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);   //  Ǔǔĉˆșʒ
}

PHP 18

  函数的回来值当程序执行时存储于*EG(return_value_ptr_ptr)。ZEND内核对值返回跟援返回作了别,并且以斯基础及针对常量,临时变量和其它项目的变量在回来时发了不同的拍卖。在return执行了事后,ZEND内核通过调用zend_leave_helper_SPEC函数,清除函数内部采用的变量等。这吗是ZEND内核自动为函数加上NULL返回的因由有。

相关文章