函数参数在内核实现上与函数内的局部变量实际是一样的,上一篇我们介绍编译的时候提供局部变量会有一个单独的 编号 ,而函数的参数与之相同,参数名称也在zend_op_array.vars中,编号首先是从参数开始的,所以按照参数顺序其编号依次为0、1、2...(转化为相对内存偏移量就是96、112、128...),然后函数调用时首先会在调用位置将参数的value复制到各参数各自的位置,详细的传参过程我们在执行一篇再作说明。
比如:
function my_function($a, $b = "aa"){
$ret = $a . $b;
return $ret;
}
编译完后各变量的内存偏移量编号:
$a => 96
$b => 112
$ret => 128
与下面这么写一样:
function my_function(){
$a = NULL;
$b = "aa";
$ret = $a . $b;
return $ret;
}
另外参数还有其它的信息,这些信息通过zend_arg_info
结构记录:
typedef struct _zend_arg_info {
zend_string *name; //参数名
zend_string *class_name;
zend_uchar type_hint; //显式声明的参数类型,比如(array $param_1)
zend_uchar pass_by_reference; //是否引用传参,参数前加&的这个值就是1
zend_bool allow_null; //是否允许为NULL,注意:这个值并不是用来表示参数是否为必传的
zend_bool is_variadic; //是否为可变参数,即...用法,与golang的用法相同,5.6以上新增的一个用法:function my_func($a, ...$b){...}
} zend_arg_info;
每个参数都有一个上面的结构,所有参数的结构保存在zend_op_array.arg_info
数组中,这里有一个地方需要注意:zend_op_array->arg_info
数组保存的并不全是输入参数,如果函数声明了返回值类型则也会为它创建一个zend_arg_info
,这个结构在arg_info数组的第一个位置,这种情况下zend_op_array->arg_info
指向的实际是数组的第二个位置,返回值的结构通过zend_op_array->arg_info[-1]
读取,这里先单独看下编译时的处理:
//函数参数的编译
void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast)
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_op_array *op_array = CG(active_op_array);
zend_arg_info *arg_infos;
if (return_type_ast) {
//声明了返回值类型:function my_func():array{...}
//多分配一个zend_arg_info
arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0);
...
arg_infos->allow_null = 0;
...
//arg_infos指向了下一个位置
arg_infos++;
op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
} else {
//没有声明返回值类型
if (list->children == 0) {
return;
}
arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0);
}
...
op_array->num_args = list->children;
//声明了返回值的情况下arg_infos已经指向了数组的第二个元素
op_array->arg_info = arg_infos;
}