函数,通俗的讲就是一组操作的集合,给予特定的输入将对应特定的输出。
用户自定义函数是指我们在PHP脚本通过function定义的函数:
function my_func(){ ... }
汇编中函数对应的是一组独立的汇编指令,然后通过call指令实现函数的调用。前面已经说过PHP编译的结果是opcode数组,与汇编指令对应。PHP用户自定义函数的实现就是将函数编译为独立的opcode数组,调用时分配独立的执行栈依次执行opcode,所以自定义函数对于zend而言并没有什么特别之处,只是将opcode进行了打包封装。PHP脚本中函数之外的指令,整个可以认为是一个函数(或者理解为main函数更直观)。
/* function main(){ */ $a = 123; echo $a; /* } */
下面具体看下PHP中函数的结构:
typedef union _zend_function zend_function; //zend_compile.h union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ uint32_t fn_flags; zend_string *function_name; zend_class_entry *scope; //成员方法所属类,面向对象实现中用到 union _zend_function *prototype; uint32_t num_args; //参数数量 uint32_t required_num_args; //必传参数数量 zend_arg_info *arg_info; //参数信息 } common;
这一节我们分析下PHP的解析阶段,即 PHP代码->抽象语法树(AST) 的过程。
PHP使用re2c、bison完成这个阶段的工作:
例如:
$a = 2 + 3;
词法分析器将上面的语句分解为这些token:$a、=、2、+、3,接着语法分析器确定了2+3
是一个表达式,而这个表达式被赋值给了a
,我们可以这样定义词法解析规则:
/*!re2c LABEL [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]* LNUM [0-9]+ //规则 "$"{LABEL} {return T_VAR;} {LNUM} {return T_NUM;} */
然后定义语法解析规则:
//token定义 %token T_VAR %token T_NUM //语法规则 statement: T_VAR '=' T_NUM '+' T_NUM {ret = str2int($3) + str2int($5);printf("%d",ret);} ;
上面的语法规则只能识别两个数值相加,假如我们希望支持更复杂的运算,比如:
$a = 3 + 4 - 6;
则可以配置递归规则:
//语法规则 statement: T_VAR '=' expr {} ; expr: T_NUM {...} |expr '?' T_NUM {} ;
这样将支持若干表达式,用语法分析树表示:
接下来我们看下PHP具体的解析过程,PHP编译阶段流程:
其中 zendparse() 就是词法、语法解析过程,这个函数实际就是bison中提供的语法解析函数 yyparse() :
#define yyparse zendparse
yyparse() 不断调用 yylex() 得到token,然后根据token匹配语法规则:
#define yylex zendlex //zend_compile.c int zendlex(zend_parser_stack_elem *elem) { zval zv; int