深入理解PHP内核(六)函数的定义、传参及返回值

一、函数的定义

  用户函数的定义从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文件中找到函数的声明过程标记如下:

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); }
;

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

  T_FUNCTION标记只是用来定位函数的声明,表示这是一个函数,而更多的工作是与这个函数相关的东西,包括参数,返回值。

  3、生成中间代码

  语法解析后,我们看到所执行编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其实现如下:

void zend_do_begin_function_declaration(znode *function_token, znode
*function_name,
 int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{
*/
{
    ...//省略
    function_token->u.op_array = CG(active_op_array);
    lcname = zend_str_tolower_dup(name, name_len);

    orig_interactive = CG(interactive);
    CG(interactive) = 0;
    init_op_array(&op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE
TSRMLS_CC);
    CG(interactive) = orig_interactive;

     ...//省略

    if (is_method) {
        ...//省略,类方法 在后面的章节介绍
?!?GH
    } else {
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);

        opline->opcode = ZEND_DECLARE_FUNCTION;
        opline->op1.op_type = IS_CONST;
        build_runtime_defined_function_key(&opline->op1.u.constant, lcname,
            name_len TSRMLS_CC);
        opline->op2.op_type = IS_CONST;
        opline->op2.u.constant.type = IS_STRING;
        opline->op2.u.constant.value.str.val = lcname;
        opline->op2.u.constant.value.str.len = name_len;
        Z_SET_REFCOUNT(opline->op2.u.constant, 1);
        opline->extended_value = ZEND_DECLARE_FUNCTION;
        zend_hash_update(CG(function_table), opline-
>op1.u.constant.value.str.val,
            opline->op1.u.constant.value.str.len, &op_array,
sizeof(zend_op_array),
             (void **) &CG(active_op_array));
    }

}
/* }}} */

  生成的代码为ZEND_DECLARE_FUNCTION,根据这个中间的代码及操作数对应的op_type。我们可以找到中间代码的执行函数为ZEND_DECLARE_FUNCTION_SPEC_HANDLER。

    在生成中间代码的时候,可以看到已经统一了函数名全部为小写,表示函数的名称不是区  分大小写的。

  为验证这个实现,我们看一段代码

function T() {
    echo 1;
}

function t() {
    echo 2;
}

  执行代码会报错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函数来实现的,在此函数中对于参数的关键代码如下:

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;

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

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;

  参数的值传递和参数传递的区别是通过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函数的实现,其代码如下:

/* {{{ 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);
    }
}
/* }}} */

  在存在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函数为例,其参数处理部分的代码如下:

/* {{{ 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;
    }
    ... //省略
}

  这里包括了两个操作:一个是取参数的个数,一个是解析参数列表。

  (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_parameters。它可以大大简化参数的接收处理工作,虽然它在处理可变参数时还有点弱。

  其声明如下:

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函数。

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);
}
/* }}} */

  生成中间代码为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为例说明函数返回值的执行过程:

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Á\[email protected]?Á??
        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);   //  ǔ
ǔ????
}

  函数的返回值在程序执行时存储在*EG(return_value_ptr_ptr)。ZEND内核对值返回和引用返回作了区别,并且在此基础上对常量,临时变量和其他类型的变量在返回时作了不同的处理。在return执行完之后,ZEND内核通过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。这也是ZEND内核自动给函数加上NULL返回的原因之一。

时间: 2024-10-11 11:16:39

深入理解PHP内核(六)函数的定义、传参及返回值的相关文章

&lt;10&gt; 无参无返回值+ 无参有返回值函数的定义+有参无返回值函数定义+ 有参有返回值函数定义+函数的参数+函数的参数传递过程

无参无返回值: 1 #include <stdio.h> 2 3 4 /** 5 * 定义一个无参无返值函数 6 * 7 */ 8 void print_line(){ 9 10 printf("-----------------\n"); 11 12 } 13 14 15 16 int main(int argc, const char * argv[]) { 17 //调用函数 18 print_line(); 19 20 return 0; 21 } 无参有返回值函数

javascript函数(声明,传参,返回值,递归)

javascript函数(声明,传参,返回值,递归) 1.函数的基本概念 函数:是由事件驱动的或者当他被调用时可执行的可重复使用的代码块. 空调是由遥控器控制或者当开关打开时,可运行的家用电器(工具) 特点: 封装代码----使代码更简洁 重复使用---在重复功能的时候直接调用就好 执行时机---随时可以在我们想要执行的时候执行 2.函数的创建和执行 1. 函数的创建 1.声明式 函数声明的关键字 : ==function== function 关键字 和 var 关键字的行为几乎一致,都会在内

初识函数(定义,语法,返回值,参数)

1.什么是函数 函数是对代码块和功能的封装和定义 2.函数的语法和定义 使用def关键字来定义函数,函数的定义语法是 def 函数名(): 函数体 函数名的命名规则和变量一样 函数体就是函数被执行之后需要执行的代码 函数的调用: 写法:函数名(),这个时候函数的函数体会被执行 3.关于函数的返回值 return:返回 1.当程序没写过return,或者写了return但是没写值,不返回任何结果,如果非要接收,接收到的是None 2.当函数写了return 值,有一个返回值 3.当函数写了retu

JavaScript函数概述、声明、return 返回值

一.函数的概述: 1.函数是定义一次但却可以调用或执行任意多次的一段 JS 代码.  2.函数有时会有参数,即函数被调用时指定了值的局部变量.  3.函数常常使用这些参数来计算一个返回值,这个值也成为函数调用表达式的值.(简单的说就是完成一个特定功能的代码块).  4.在 javaScript 中,Function(函数)类型实际上是对象.每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法.  5.由于函数是对象,因此函数名实际上也是一个指向函数对象的指针. 6.

python 最简单的函数(无参无返回值)

#定义了一个无参无返回值的函数def myPrint(): print("sunck is a very good man!") print("sunck is a nice man!") print("sunck is a handsome man!") myPrint()myPrint()myPrint()myPrint()myPrint() 原文地址:https://www.cnblogs.com/pygo/p/12243029.html

定义一个带参带返回值的方法,实现输出随机数数组

public class mains { public static void main(String[] args) { // 创建对象,对象名为hello mains hello = new mains(); // 调用方法并将返回值保存在变量中 int[] nums = hello.getArray(8); // 将数组转换为字符串并输出 System.out.println(Arrays.toString(nums)); } /* * 功能:创建指定长度的int型数组,并生成100以内随

C#中的函数(二) 有参有返回值的函数

接上一篇 C#中的函数(-) 无参无返回值的函数 http://www.cnblogs.com/fzxiaoyi/p/8502613.html 这次研究下C#中的函数(二) 有参有返回值的函数 依然写一个小例子,用来测试 跟上一个例子差不多,区别就是MyFunction有二个参数a,b,返回二个数相加的值 F5调试运行,中断后转到反汇编 这里很明显看到不同了 这里就得讲到参数传递的方式,参数从左向右依次存入寄存器ecx edx 但是不同的编程语言有不同的传递参数的方式,有空再写一篇文章介绍下 要

深入理解PHP内核(五)函数的内部结构

php的函数包括用户定义的函数.内部函数(print_r count...).匿名函数.变量函数($func = 'print_r'; $func(array('a','b'));) PHP内核源码中将函数分为以下类型 #define ZEND_INTERNAL_FUNCTION 1 #define ZEND_USER_FUNCTION 2 #define ZEND_OVERLOADED_FUNCTION 3 #define ZEND_EVAL_CODE 4 #define ZEND_OVERL

初识python 函数(定义,传参,返回值)

python基础(二): 菜鸟教程基础知识讲解的非常全面,内容选择我认为的重点输出一遍 函数: 定义一个函数: 你可以定义一个由自己想要功能的函数,以下是简单的规则: def fun(arg): pass return arg # 函数调用语法 fun(arg) arg:argument,参数(也叫prarmeter) ,任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数. def:define,定义,创建函数 ,函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()