函数参数解析

7.6.2 函数参数解析

上面我们定义的函数没有接收任何参数,那么扩展定义的内部函数如何读取参数呢?首先回顾下函数参数的实现:用户自定义函数在编译时会为每个参数创建一个zend_arg_info结构,这个结构用来记录参数的名称、是否引用传参、是否为可变参数等,在存储上函数参数与局部变量相同,都分配在zend_execute_data上,且最先分配的就是函数参数,调用函数时首先会进行参数传递,按参数次序依次将参数的value从调用空间传递到被调函数的zend_execute_data,函数内部像访问普通局部变量一样通过存储位置访问参数,这是用户自定义函数的参数实现。

内部函数与用户自定义函数最大的不同在于内部函数就是一个普通的C函数,除函数参数以外在zend_execute_data上没有其他变量的分配,函数参数是从PHP用户空间传到函数的,它们与用户自定义函数完全相同,包括参数的分配方式、传参过程,也是按照参数次序依次分配在zend_execute_data上,所以在扩展中定义的函数直接按照顺序从zend_execute_data上读取对应的值即可,PHP中通过zend_parse_parameters()这个函数解析zend_execute_data上保存的参数:

zend_parse_parameters(int num_args, const char *type_spec, ...);
  • num_args为实际传参数,通过ZEND_NUM_ARGS()获取:zend_execute_data->This.u2.num_args,前面曾介绍过zend_execute_data->This这个zval的用途;
  • type_spec是一个字符串,用来标识解析参数的类型,比如:"la"表示第一个参数为整形,第二个为数组,将按照这个解析到指定变量;
  • 后面是一个可变参数,用来指定解析到的变量,这个值与type_spec配合使用,即type_spec用来指定解析的变量类型,可变参数用来指定要解析到的变量,这个值必须是指针。

i解析的过程也比较容易理解,调用函数时首先会把参数拷贝到调用函数的zend_execute_data上,所以解析的过程就是按照type_spec指定的各个类型,依次从zend_execute_data上获取参数,然后将参数地址赋给目标变量,比如下面这个例子:

PHP_FUNCTION(my_func_1)
{
    zend_long   lval;
    zval        *arr;

    if(zend_parse_parameters(ZEND_NUM_ARGS(), "la", &lval, &arr) == FAILURE){
        RETURN_FALSE;
    }
    ...
}

对应的内存关系:

注意:解析时除了整形、浮点型、布尔型是直接硬拷贝value外,其它解析到的变量只能是指针,arr为zend_execute_data上param_1的地址,即:zval *arr = ¶m_1,所以图中arr、param_1之间用的不是箭头指向,也就是说参数始终存储在zend_execute_data上,解析获取的是这些参数的地址。zend_parse_parameters()调用了zend_parse_va_args()进行处理,简单看下解析过程:

//va就是定义的要解析到的各个变量的地址
static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va, int flags)
{
    const  char *spec_walk;
    int min_num_args = -1; //最少参数数
    int max_num_args = 0; //要解析的参数总数
    int post_varargs = 0;
    zval *arg;
    int arg_count; //实际传参数

    //遍历type_spec计算出min_num_args、max_num_args
    for (spec_walk = type_spec; *spec_walk; spec_walk++) {
        ...
    }
    ...
    //检查数目是否合法
    if (num_args < min_num_args || (num_args > max_num_args && max_num_args >= 0)) {
        ...
    }
    //获取实际传参数:zend_execute_data.This.u2.num_args
    arg_count = ZEND_CALL_NUM_ARGS(EG(current_execute_data));
    ...
    i = 0;
    //逐个解析参数
    while (num_args-- > 0) {
        ...
        //获取第i个参数的zval地址:arg就是在zend_execute_data上分配的局部变量
        arg = ZEND_CALL_ARG(EG(current_execute_data), i + 1);

        //解析第i个参数
        if (zend_parse_arg(i+1, arg, va, &type_spec, flags) == FAILURE) {
            if (varargs && *varargs) {
                *varargs = NULL;
            }
            return FAILURE;
        }
        i++;
    }
}

接下来详细看下不同类型的解析方式。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3