php过程函数,PHP内核探索:定义函数的过程

在PHP中,用户函数的定义从function关键字开始。如下所示简单示例:

function foo($var) {

echo $var;

}

这是一个非常简单的函数,它所实现的功能是定义一个函数,函数有一个参数,函数的内容是在标准输出端输出传递给它的参数变量的值。

函数的一切从function开始。我们从function开始函数定义的探索之旅。

词法分析

在 Zend/zend_language_scanner.l中我们找到如下所示的代码:

"function" {

return T_FUNCTION;

}

它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,我们开始语法分析。

语法分析

在 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标记只是用来定位函数的声明,表示这是一个函数,而更多的工作是与这个函数相关的东西,包括参数,返回值等。

生成中间代码

语法解析后,我们看到所执行编译函数为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) {

...//省略 类方法 在后面的类章节介绍

} 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是同一个函数名。检验函数名是否重复,这个过程是在哪进行的呢? 下面将要介绍的函数声明中间代码的执行过程包含了这个检查过程。

执行中间代码

在 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。

延伸阅读

此文章所在专题列表如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值