原文连接:http://www.orlion.ga/344/node
1、函数的定义数组
用户函数的定义从function 关键字开始,以下app
function foo($var) { echo $var; }
一、词法分析函数
在Zend/zend_language_scanner.l中咱们找到以下所示的代码:ui
<ST_IN_SCRIPTING>"function" { return T_FUNCTION; }
它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,咱们开始语法分析。this
二、语法分析spa
在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关键字,是否引用,函数名code
T_FUNCTION标记只是用来定位函数的声明,表示这是一个函数,而更多的工做是与这个函数相关的东西,包括参数,返回值。blog
三、生成中间代码
语法解析后,咱们看到所执行编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其实现以下:
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, & **) &
生成的代码为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是同一个函数名,校验函数名是否重复,这个过程是在哪进行的呢?
四、执行中间代码
在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
2、函数的参数
函数的定义只是一个将函数名注册到函数列表的过程。
一、用户自定义函数的参数
咱们知道对于函数的参数检查是经过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;
二、内部函数的参数
以常见的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()在解析参数的同时户尽量的转换参数类型,这样就能够确保咱们老是能获得所指望的类型的变量
三、函数的返回值
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Á\Ɂƶ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); // Ǔǔĉșʒ }
函数的返回值在程序执行时存储在*EG(return_value_ptr_ptr)。ZEND内核对值返回和引用返回做了区别,而且在此基础上对常量,临时变量和其余类型的变量在返回时做了不一样的处理。在return执行完以后,ZEND内核经过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。这也是ZEND内核自动给函数加上NULL返回的缘由之一。