如何用数据结构常用算法与算法实现chars[30]的压缩存储,包括初始化,数据的查询与修改?

&&&&数据结构与算法(第2版)
自营订单满49元(含)免运费
不足金额订单收取运费5元起
邀请好友参加吧
版 次:2页 数:248字 数:400000印刷时间:日开 本:16开纸 张:胶版纸包 装:平装-胶订是否套装:否国际标准书号ISBN:7丛书名:21世纪高等学校规划教材·计算机科学与技术所属分类:&&&&&&
下载免费当当读书APP
下载当当读书APP,免费阅读万本电子书。
本商品暂无详情。
当当价:为商品的销售价,具体的成交价可能因会员使用优惠券、积分等发生变化,最终以订单结算页价格为准。
划线价:划线价格可能是图书封底定价、商品吊牌价、品牌专柜价或由品牌供应商提供的正品零售价(如厂商指导价、建议零售价等)或该商品曾经展示过的销售价等,由于地区、时间的差异化和市场行情波动,商品吊牌价、品牌专柜价等可能会与您购物时展示的不一致,该价格仅供您参考。
折扣:折扣指在划线价(图书定价、商品吊牌价、品牌专柜价、厂商指导价等)某一价格基础上计算出的优惠比例或优惠金额。如有疑问,您可在购买前联系客服咨询。
异常问题:如您发现活动商品销售价或促销信息有异常,请立即联系我们补正,以便您能顺利购物。
当当购物客户端手机端1元秒
当当读书客户端万本电子书免费读《数据结构与算法课程设计》课程设计备选题目_图文_百度文库
赠送免券下载特权
10W篇文档免费专享
部分付费文档8折起
每天抽奖多种福利
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
《数据结构与算法课程设计》课程设计备选题目
&&《数据结构与算法》课程设计
阅读已结束,下载本文需要
想免费下载更多文档?
定制HR最喜欢的简历
下载文档到电脑,同时保存到云知识,更方便管理
加入VIP
还剩68页未读,
定制HR最喜欢的简历
你可能喜欢当前位置: &&>> 阅读正文
View: 39,244
Author: Dong
- 359,966 阅 - 273,613 阅 - 261,861 阅 - 247,108 阅 - 245,229 阅 - 243,153 阅 - 223,096 阅 - 214,589 阅 - 211,832 阅 - 204,386 阅
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '
collapsItems['collapsArch-'] = '05-1502-1602-1602-1602-1602-1602-1602-1602-1602-16最新范文01-0101-0101-0101-0101-0101-0101-0101-0101-0101-0101-0101-0101-0101-0101-01[_get_zval_cv_lookup]
在_get_zval_cv_lookup函数中关键代码为:
zend_hash_quick_find(EG(active_symbol_table), cv-&name, cv-&name_len+1,
cv-&hash_value, (void **)ptr)
这是一个HashTable的查找函数,它的作用是从EG(active_symbol_table)中查找名称为cv->name的变量,并将这个值赋值给ptr。
最后,这个在符号表中找到的值将传递给ZEND_ASSIGN_SPEC_CV_CONST_HANDLER函数的variable_ptr_ptr变量。
以上是获取左值和右值的过程,在这步操作后将执行赋值操作的核心操作--赋值。赋值操作是通过调用zend_assign_to_variable函数实现。
在zend_assign_to_variable函数中,赋值操作分为好几种情况来处理,在程序中就是以几层的if语句体现。
情况一:赋值的左值存在引用(即zval变量中is_ref__gc字段不为0),并且左值不等于右值
这种情形描述起来比较抽象,如下面的示例:
xdebug_debug_zval('a');
xdebug_debug_zval('a');
试想,如果我们来做这个$b = &$a;的底层实现,我们可能会这样做:
判断左值是不是已经被引用过了;
左值已经被引用,则不改变左值的引用计数,将右值赋与左值;
事实上,ZE也是用同样的方法来实现,其代码如下:
if (PZVAL_IS_REF(variable_ptr)) {
if (variable_ptr!=value) {
zend_uint refcount = Z_REFCOUNT_P(variable_ptr);
garbage = *variable_ptr;
*variable_ptr = *value;
Z_SET_REFCOUNT_P(variable_ptr, refcount);
Z_SET_ISREF_P(variable_ptr);
if (!is_tmp_var) {
zendi_zval_copy_ctor(*variable_ptr);
zendi_zval_dtor(garbage);
return variable_ptr;
PZVAL_IS_REF(variable_ptr)判断is_ref__gc字段是否为0。在左值不等于右值的情况下执行操作。
所有指向这个zval容器的变量的值都变成了*value。并且引用计数的值不变。下面是这种情况的一个示例:
上面的例子的输出结果:
(refcount=2, is_ref=1),int 10
(refcount=2, is_ref=1),int 20
情况二:赋值的左值不存在引用,左值的引用计数为1,左值等于右值
在这种情况下,应该是什么都不会发生吗?看一个示例:
看上去真的像是什么都没有发生,
左值的引用计数还是1,值仍是10 。
然而在这个赋值过程中,$a的引用计数经历了一次加一和一次减一的操作。
如以下代码:
if (Z_DELREF_P(variable_ptr)==0) {
引用计数减一操作
if (!is_tmp_var) {
if (variable_ptr==value) {
Z_ADDREF_P(variable_ptr);
引用计数加一操作
情况三:赋值的左值不存在引用,左值的引用计数为1,右值存在引用
用一个PHP的示例来描述一下这种情况:
这里的$c = $a;的操作就是我们所示的第三种情况。
对于这种情况,ZEND内核直接创建一个新的zval容器,左值的值为右值,并且左值的引用计数为1。
也就是说,这种情形$c不会与$a指向同一个zval。
其内核实现代码如下:
garbage = *variable_ptr;
*variable_ptr = *value;
INIT_PZVAL(variable_ptr);
初始化一个新的zval变量容器
zval_copy_ctor(variable_ptr);
zendi_zval_dtor(garbage);
return variable_ptr;
在这个例子中,若将 $c = $a; 换成 $c = &$a;,$a,$b和$c三个变量的引用计数会发生什么变化?
将 $b = &$a; 换成 $b = $a; 呢?
大家可以将答案回复在下面:)
情况四:赋值的左值不存在引用,左值的引用计数为1,右值不存在引用
这种情形如下面的例子:
这时,右值的引用计数加上,一般情况下,会对左值进行垃圾收集操作,将其移入垃圾缓冲池。垃圾缓冲池的功能是在PHP5.3后才有的。
在PHP内核中的代码体现为:
Z_ADDREF_P(value);
引用计数加1
*variable_ptr_ptr = value;
if (variable_ptr != &EG(uninitialized_zval)) {
GC_REMOVE_ZVAL_FROM_BUFFER(variable_ptr);
调用垃圾收集机制
zval_dtor(variable_ptr);
efree(variable_ptr);
释放变量内存空间
return value;
情况五:赋值的左值不存在引用,左值的引用计数为大于0,右值存在引用,并且引用计数大于0
一个演示这种情况的PHP示例:
$vb = &$va;
最后一个操作就是我们的情况五。
使用xdebug看引用计数发现,最终$a变量的引用计数为1,$va变量的引用计数为2,并且$va存在引用。
从源码层分析这个原因:
ALLOC_ZVAL(variable_ptr);
分配新的zval容器
*variable_ptr_ptr = variable_ptr;
*variable_ptr = *value;
zval_copy_ctor(variable_ptr);
Z_SET_REFCOUNT_P(variable_ptr, 1);
设置引用计数为1
从代码可以看出是新分配了一个zval容器,并设置了引用计数为1,印证了我们之前的例子$a变量的结果。
除上述五种情况之外,zend_assign_to_variable函数还对全部的临时变量做了处理。
变量赋值的各种操作全部由此函数完成。
变量的销毁
在PHP中销毁变量最常用的方法是使用unset函数。
unset函数并不是一个真正意义上的函数,它是一种语言结构。
在使用此函数时,它会根据变量的不同触发不同的操作。
一个简洁的例子:
($a);
使用VLD扩展查看其生成的中间代码:
compiled vars:
---------------------------------------------------------------------------------
去掉关于赋值的中间代码,得到unset函数生成的中间代码为 UNSET_VAR,由于我们unset的是一个变量,
在Zend/zend_vm_execute.h文件中查找到其最终调用的执行中间代码的函数为: ZEND_UNSET_VAR_SPEC_CV_HANDLER
关键代码代码如下:
target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts),
BP_VAR_IS, varname TSRMLS_CC);
if (zend_hash_quick_del(target_symbol_table, varname-&value.str.val,
varname-&value.str.len+1, hash_value) == SUCCESS) { //
删除HashTable元素
zend_execute_data *ex = execute_data;
if (ex-&op_array) {
for (i = 0; i & ex-&op_array-&last_var; i++) {
if (ex-&op_array-&vars[i].hash_value == hash_value &&
ex-&op_array-&vars[i].name_len == varname-&value.str.len &&
!memcmp(ex-&op_array-&vars[i].name, varname-&value.str.val, varname-&value.str.len)) {
ex-&CVs[i] = NULL; // 置空EX(CVs)
ex = ex-&prev_execute_data;
} while (ex && ex-&symbol_table == target_symbol_table);
程序会先获取目标符号表,这个符号表是一个HashTable,然后将我们需要unset掉的变量从这个HashTable中删除。
如果对HashTable的元素删除操作成功,程序还会对EX(CVs)内存储的值进行清空操作。
以缓存机制来解释,在删除原始数据后,程序也会删除相对应的缓存内容,以免用户获取到脏数据。
变量的销毁还涉及到垃圾回收机制(GC),请参见相关第六章内容
关于HashTable的操作请参考 。
变量的作用域
TODO FIXME 需要调整,不够明确
变量的作用域是变量的一个作用范围,在这个范围内变量为可见的,即可以访问该变量的代码区域,
相反,如果不在这个范围内,变量是不可见的,无法被访问到。
如下面的例子:(会输出什么样的结果呢?)
$foo = 'tipi';
function variable_scope(){
$foo = 'foo';
全局变量与局部变量
变量按作用域类型分为:全局变量和局部变量。全局变量是在整个程序中任何地方随意调用的变量,
在PHP中,除了声明在函数体内的普通变量均为全局变量,在函数体内则可以通过global语句来声明。
相对于全局变量,局部变量的作用域是程序中的部分代码(如函数中),而不是程序的全部。
变量的作用域与变量的生命周期有一定的联系,
如在一个函数中定义的变量,
这个变量的作用域从变量声明的时候开始到这个函数结束的时候。
这种变量我们称之为局部变量。它的生命周期开始于函数开始,结束于函数的调用完成之时。
变量的作用域决定其生命周期吗?程序运行到变量作用域范围之外,就会将变量进行销毁吗?
对于不同作用域的变量,如果存在冲突情况,就像上面的例子中,全局变量中有一个名为$bar的变量,
在局部变量中也存在一个名为$bar的变量,
此时如何区分呢?
对于全局变量,Zend引擎有一个_zend_executor_globals结构,该结构中的symbol_table就是全局符号表,
其中保存了在顶层作用域中的变量。同样,函数或者对象的方法在被调用时会创建active_symbol_table来保存局部变量。
当程序在顶层中使用某个变量时,ZE就会在symbol_table中进行遍历,
同理,如果程序运行于某个函数中,Zend引擎会遍历查询与其对应的active_symbol_table,
而每个函数的active_symbol_table是相对独立的,由此而实现的作用域的独立。
展开来看,如果我们调用的一个函数中的变量,ZE使用_zend_execute_data来存储
某个单独的op_array(每个函数都会生成单独的op_array)执行过程中所需要的信息,它的结构如下:
struct _zend_execute_data {
struct _zend_op *opline;
zend_function_state function_state;
zend_function *fbc; /* Function Being Called */
zend_class_entry *called_scope;
zend_op_array *op_array;
zval *object;
union _temp_variable *Ts;
zval ***CVs;
HashTable *symbol_table;
struct _zend_execute_data *prev_execute_data;
zval *old_error_reporting;
zend_bool nested;
zval **original_return_value;
zend_class_entry *current_scope;
zend_class_entry *current_called_scope;
zval *current_this;
zval *current_object;
struct _zend_op *call_opline;
函数中的局部变量就存储在_zend_execute_data的symbol_table中,在执行当前函数的op_array时,
全局zend_executor_globals中的*active_symbol_table会指向当前_zend_execute_data中的*symbol_table。
因为每个函数调用开始时都会重新初始化EG(active_symbol_table)为NULL,
在这个函数的所有opcode的执行过程中这个全局变量会一直存在,并且所有的局部变量修改都是在它上面操作完成的,如前面的赋值操作等。
而此时,其他函数中的symbol_table会存放在栈中,将当前函数执行完并返回时,程序会将之前保存的zend_execute_data恢复,
从而其他函数中的变量也就不会被找到,局部变量的作用域就是以这种方式来实现的。
相关操作在 Zend/zend_vm_execute.h 文件中定义的execute函数中一目了然,如下所示代码:
zend_vm_enter:
/* Initialize execute_data */
execute_data = (zend_execute_data *)zend_vm_stack_alloc(
sizeof(zend_execute_data) +
sizeof(zval**) * op_array-&last_var * (EG(active_symbol_table) ? 1 : 2) +
sizeof(temp_variable) * op_array-&T TSRMLS_CC);
EX(symbol_table) = EG(active_symbol_table);
EX(prev_execute_data) = EG(current_execute_data);
EG(current_execute_data) = execute_data;
所以,变量的作用域是使用不同的符号表来实现的,于是顶层的全局变量在函数内部使用时,
需要先使用global语句来将变量“挪”到函数独立的*active_symbol_table中,
即变量的跨域操作。(关于global的详细解释,见下一小节)
在PHP的源码中,EX宏经常出现,它的作用是获取结构体zend_execute_data的字段值,它的实现是:
#define EX(element) execute_data->element
global语句
global语句的作用是定义全局变量,例如如果想在函数内访问全局作用域内的变量则可以通过global声明来定义。
下面从语法解释开始分析。
1. 词法解析
查看 Zend/zend_language_scanner.l文件,搜索 global关键字。我们可以找到如下代码:
&ST_IN_SCRIPTING&&global& {
return T_GLOBAL;
2. 语法解析
在词法解析完后,获得了token,此时通过这个token,我们去Zend/zend_language_parser.y文件中查找。找到相关代码如下:
T_GLOBAL global_var_list ';'
global_var_list:
global_var_list ',' global_var
{ zend_do_fetch_global_variable(&$3, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); }
global_var
{ zend_do_fetch_global_variable(&$1, NULL, ZEND_FETCH_GLOBAL_LOCK TSRMLS_CC); }
上面代码中的$3是指global_var(如果不清楚yacc的语法,可以查阅yacc入门类的文章。)
从上面的代码可以知道,对于全局变量的声明调用的是zend_do_fetch_global_variable函数,查找此函数的实现在Zend/zend_compile.c文件。
void zend_do_fetch_global_variable(znode *varname, const znode *static_assignment, int fetch_type TSRMLS_DC)
opline-&opcode = ZEND_FETCH_W;
/* the default mode must be Write, since fetch_simple_variable() is used to define function arguments */
opline-&result.op_type = IS_VAR;
opline-&result.u.EA.type = 0;
opline-&result.u.var = get_temporary_variable(CG(active_op_array));
opline-&op1 = *varname;
SET_UNUSED(opline-&op2);
opline-&op2.u.EA.type = fetch_type;
result = opline-&result;
... // 省略
fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact that the default fetch is BP_VAR_W */
zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC);
CG(active_op_array)-&opcodes[CG(active_op_array)-&last-1].result.u.EA.type |= EXT_TYPE_UNUSED;
上面的代码确认了opcode为ZEND_FETCH_W外,还执行了zend_do_assign_ref函数。zend_do_assign_ref函数的实现如下:
void zend_do_assign_ref(znode *result, const znode *lvar, const znode *rvar TSRMLS_DC) /* {{{ */
zend_op *opline;
... //省略
opline = get_next_op(CG(active_op_array) TSRMLS_CC);
opline-&opcode = ZEND_ASSIGN_REF;
if (result) {
opline-&result.op_type = IS_VAR;
opline-&result.u.EA.type = 0;
opline-&result.u.var = get_temporary_variable(CG(active_op_array));
*result = opline-&result;
} else {
/* SET_UNUSED(opline-&result); */
opline-&result.u.EA.type |= EXT_TYPE_UNUSED;
opline-&op1 = *lvar;
opline-&op2 = *rvar;
从上面的zend_do_fetch_global_variable函数和zend_do_assign_ref函数的实现可以看出,
使用global声明一个全局变量后,其执行了两步操作,ZEND_FETCH_W和ZEND_ASSIGN_REF。
3. 生成并执行中间代码
我们看下ZEND_FETCH_W的最后执行。从代码中我们可以知道:
ZEND_FETCH_W = 83
op->op1.op_type = 4
op->op2.op_type = 0
而计算最后调用的方法在代码中的体现为:
zend_opcode_handlers[opcode * 25 + zend_vm_decode[op-&op1.op_type] * 5 + zend_vm_decode[op-&op2.op_type]];
计算,最后调用ZEND_FETCH_W_SPEC_CV_HANDLER函数。即
static int ZEND_FASTCALL
ZEND_FETCH_W_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
return zend_fetch_var_address_helper_SPEC_CV(BP_VAR_W, ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
在zend_fetch_var_address_helper_SPEC_CV中调用如下代码获取符号表
target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts), type, varname TSRMLS_CC);
在zend_get_target_symbol_table函数的实现如下:
static inline HashTable *zend_get_target_symbol_table(const zend_op *opline, const temp_variable *Ts, int type, const zval *variable TSRMLS_DC)
switch (opline-&op2.u.EA.type) {
case ZEND_FETCH_GLOBAL:
case ZEND_FETCH_GLOBAL_LOCK:
return &EG(symbol_table);
return NULL;
在前面语法分析过程中,程序传递的参数是 ZEND_FETCH_GLOBAL_LOCK,于是如上所示。我们取&EG(symbol_table);的值。这也是全局变量的存放位置。
如上就是整个global的解析过程。
第七节 数据类型转换
PHP是弱类型的动态语言,在前面的章节中我们已经介绍了PHP的变量都存放在一个名为ZVAL的容器中,
ZVAL包含了变量的类型和各种类型变量的值。
PHP中的变量不需要显式的数据类型定义,可以给变量赋值任意类型的数据,
PHP变量之间的数据类型转换有两种: 隐式和显式转换。
隐式类型转换
隐式类型转换也被称为自动类型转换,是指不需要程序员书写代码,由编程语言自动完成的类型转换。
在PHP中,我们经常遇到的隐式转换有:
1.直接的变量赋值操作
在PHP中,直接对变量的赋值操作是隐式类型转换最简单的方式,也是我们最常见的一种方式,或许我们已经习以为常,从而没有感觉到变量的变化。
在直接赋值的操作中,变量的数据类型由赋予的值决定,即左值的数据类型由右值的数据类型决定。
比如,当把一个字符串类型的数据赋值给变量时,不管该变量以前是什么类型的变量,此时该变量就是一个字符串类型的变量。
看一段代码:
$string = &To love someone sincerely means to love all the people,
to love the world and life,
$integer = 10;
$string = $integer;
上面的代码,当执行完第三行代码,$string变量的类型就是一个整形了。
通过VLD扩展可以查到第三次赋值操作的中间代码及操作数的类型,再找到赋值的最后实现为zend_assign_to_variable函数。
这在前面的小节中已经详细介绍过了。我们这个例子是很简单的一种赋值,在源码中是直接将$string的ZVAL容器的指针指向$integer变量指向的指针,
并将$integer的引用计数加1。这个操作在本质上改变了$string变量的内容,而原有的变量内容则被垃圾收集机制回收。关于赋值的具体细节,请返回查看。
2.运算式结果对变量的赋值操作
我们常说的隐式类型转换是将一个表达式的结果赋值给一个变量,在运算的过程中发生了隐式的类型转换。
这种类型转换不仅仅在PHP语言,在其它众多的语言中也有见到,这是我们常规意义上的隐式类型转换。
这种类型转换又分为两种情况:
表达式的操作数为同一数据类型 这种情况的作用以上面的直接变量的类型转换是同一种情况,只是此时右值变成了表达式的运算结果。
表达式的操作数不为同的数据类型 这种情况的类型转换发生在表达式的运算符的计算过程中,在源码中也就是发生在运行符的实现过程中。
看一个字符串和整数的隐式数据类型转换:
$b = 'a string ';
上面例子中字符串连接操作就存在自动数据类型转化,$a变量是数值类型,$b变量是字符串类型,
这里$a变量就是隐式(自动)的转换为字符串类型了。通常自动数据类型转换发生在特定的操作上下文中,
类似的还有求和操作"+"。具体的自动类型转换方式和特定的操作有关。
下面就以字符串连接操作为例说明隐式转换的实现:
脚本执行的时候字符串的连接操作是通过Zend/zend_operators.c文件中的如下函数进行:
ZEND_API int concat_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
zval op1_copy, op2_copy;
int use_copy1 = 0, use_copy2 = 0;
if (Z_TYPE_P(op1) != IS_STRING) {
zend_make_printable_zval(op1, &op1_copy, &use_copy1);
if (Z_TYPE_P(op2) != IS_STRING) {
zend_make_printable_zval(op2, &op2_copy, &use_copy2);
可用看出如果字符串链接的两个操作数如果不是字符串的话,
则调用zend_make_printable_zval函数将操作数转换为"printable_zval"也就是字符串。
ZEND_API void zend_make_printable_zval(zval *expr, zval *expr_copy, int *use_copy)
if (Z_TYPE_P(expr)==IS_STRING) {
*use_copy = 0;
switch (Z_TYPE_P(expr)) {
case IS_NULL:
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
case IS_BOOL:
if (Z_LVAL_P(expr)) {
Z_STRLEN_P(expr_copy) = 1;
Z_STRVAL_P(expr_copy) = estrndup(&1&, 1);
} else {
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
case IS_RESOURCE:
// ...省略
case IS_ARRAY:
Z_STRLEN_P(expr_copy) = sizeof(&Array&) - 1;
Z_STRVAL_P(expr_copy) = estrndup(&Array&, Z_STRLEN_P(expr_copy));
case IS_OBJECT:
// ... 省略
case IS_DOUBLE:
*expr_copy = *expr;
zval_copy_ctor(expr_copy);
zend_locale_sprintf_double(expr_copy ZEND_FILE_LINE_CC);
*expr_copy = *expr;
zval_copy_ctor(expr_copy);
convert_to_string(expr_copy);
Z_TYPE_P(expr_copy) = IS_STRING;
*use_copy = 1;
这个函数根据不同的变量类型来返回不同的字符串类型,例如BOOL类型的数据返回0和1,
数组只是简单的返回Array等等,类似其他类型的数据转换也是类型,
都是根据操作数的不同类型的转换为相应的目标类型。在表达式计算完成后,表达式最后会有一个结果,
这个结果的数据类型就是整个表达式的数据类型。当执行赋值操作时,如果再有数据类型的转换发生,
则是直接变量赋值的数据类型转换了。
显式类型转换(强制类型转换)
在前面介绍了隐式类型转换,在我们的日常编码过程也会小心的使用这种转换,
这种不可见的操作可能与我们想象中的不一样,如整形和浮点数之间的转换。
当我们是一定需要某个数据类型的变量时,可以使用强制的数据类型转换,这样在代码的可读性等方面都会好些。
在PHP中的强制类型转换和C中的非常像:
$double = 20.10;
(int)$double;
PHP中允许的强制类型有:
(int), (integer)
转换为整型
(bool), (boolean) 转换为布尔类型
(float), (double) 转换为浮点类型
(string) 转换为字符串
(array) 转换为数组
(object) 转换为对象
(unset) 转换为NULL
在Zend/zend_operators.c中实现了转换为这些目标类型的实现函数convert_to_*系列函数,
读者自行查看这些函数即可,这些数据类型转换类型中有一个我们比较少见的unset类型转换:
ZEND_API void convert_to_null(zval *op) /* {{{ */
if (Z_TYPE_P(op) == IS_OBJECT) {
if (Z_OBJ_HT_P(op)-&cast_object) {
zval *org;
TSRMLS_FETCH();
ALLOC_ZVAL(org);
*org = *op;
if (Z_OBJ_HT_P(op)-&cast_object(org, op, IS_NULL TSRMLS_CC) == SUCCESS) {
zval_dtor(org);
*op = *org;
FREE_ZVAL(org);
zval_dtor(op);
Z_TYPE_P(op) = IS_NULL;
转换为NULL非常简单,对变量进行析构操作,然后将数据类型设为IS_NULL即可。
可能读者会好奇(unset)$a和unset($a)这两者有没有关系,其实并没有关系,
前者是将变量$a的类型变为NULL,这只是一个类型的变化,而后者是将这个变量释放,释放后当前作用域内该变量就不存在了。
除了上面提到的与C语言很像,在其它语言中也经常见到的强制数据转换,PHP中有一个极具PHP特色的强制类型转换。
PHP的标准扩展中提供了两个有用的方法settype()以及gettype()方法,前者可以动态的改变变量的数据类型,
gettype()方法则是返回变量的数据类型。在ext/standard/type.c文件中找到settype的实现源码:
PHP_FUNCTION(settype)
zval **var;
char *type;
int type_len = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, &Zs&, &var, &type, &type_len) == FAILURE) {
if (!strcasecmp(type, &integer&)) {
convert_to_long(*var);
} else if (!strcasecmp(type, &int&)) {
convert_to_long(*var);
} else if (!strcasecmp(type, &float&)) {
convert_to_double(*var);
} else if (!strcasecmp(type, &double&)) { /* deprecated */
convert_to_double(*var);
} else if (!strcasecmp(type, &string&)) {
convert_to_string(*var);
} else if (!strcasecmp(type, &array&)) {
convert_to_array(*var);
} else if (!strcasecmp(type, &object&)) {
convert_to_object(*var);
} else if (!strcasecmp(type, &bool&)) {
convert_to_boolean(*var);
} else if (!strcasecmp(type, &boolean&)) {
convert_to_boolean(*var);
} else if (!strcasecmp(type, &null&)) {
convert_to_null(*var);
} else if (!strcasecmp(type, &resource&)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, &Cannot convert to resource type&);
RETURN_FALSE;
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, &Invalid type&);
RETURN_FALSE;
RETVAL_TRUE;
这个极具PHP特色的强制类型转换就是这个函数,而这个函数是作为一个代理方法存在,
具体的转换规则由各个类型的处理函数处理,不管是自动还是强制类型转换,最终都会调用这些内部转换方法,
这和前面的强制类型转换在本质上是一样的。
第八节 小结
在命令式程序语言中,程序的变量是程序语言对计算机的存储单元或一系列存储单元的抽象。
在PHP语言中,变量是对于C语言结构体和一系列结构体的抽象。但是追根究底,它也是对计算机的存储单元或一系列存储单元的抽象。
在这一章中,我们向您展示了PHP实现的内部结构,常量,预定义变量,静态变量等常见变量的实现,除此之外,还介绍了在PHP5之后才有的类型提示,变量的作用域以及类型的转换。
下一章将探索PHP对于函数的实现。
第四章 函数的实现
函数是一种可以在任何被需要的时候执行的代码块。它不仅仅包括用户自定义的函数,还包括程序语言实现的库函数。
用户定义的函数
如下所示手册中的展示函数用途的伪代码
function foo($arg_1, $arg_2, ..., $arg_n) {
&Example function.\n&;
return $retval;
任何有效的 PHP 代码都可以编写在函数内部,甚至包括其它函数和类定义。
在 PHP 3 中,函数必须在被调用之前定义。而 PHP 4 则不再有这样的条件。除非函数如以下两个范例中有条件的定义。
PHP 有很多标准的函数和结构。如我们常见的count、strpos、implode等函数,这些都是标准函数,它们都是由标准扩展提供的;
如我们经常用到的isset、empty、eval等函数,这些结构被称之为语言结构。
还有一些函数需要和特定的PHP扩展模块一起编译并开启,否则无法使用。也就是有些扩展是可选的。
标准函数的实现存放在ext/standard扩展目录中。
有时我们的一代代码并不需要为它指定一个名称,而只需要它完成特定的工作,
匿名函数的作用是为了扩大函数的使用功能,在PHP 5.3以前,传递函数回调的方式,我们只有两种选择:
字符串的函数名
使用create_function创建的返回
在PHP5.3以后,我们多了一个选择--Closure。在实现上PHP 5.3中对匿名函数的支持,采用的是把要保持的外部变量,
做为Closure对象的”Static属性”来实现的,关于如何实现我们将在后面的章节介绍。
PHP 支持变量函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,
并且将尝试执行它。除此之外,这个可以被用于实现回调函数,函数表等。
一个变量函数的简单例子:
$func = 'print_r';
$func('i am print_r function.');
变量函数不能用于语言结构(echo等)
下面我们将开始关注函数在PHP中具体实现,函数的内部结构,函数的调用,参数传递以及函数返回值等。
第一节 函数的内部结构
函数的内部结构
在PHP中,函数有自己的作用域,同时在其内部可以实现各种语句的执行,最后返回最终结果值。
在PHP的源码中可以发现,Zend引擎将函数分为以下类型:
#define ZEND_INTERNAL_FUNCTION
#define ZEND_USER_FUNCTION
#define ZEND_OVERLOADED_FUNCTION
#define ZEND_EVAL_CODE
#define ZEND_OVERLOADED_FUNCTION_TEMPORARY
其中的ZEND_USER_FUNCTION是用户函数,ZEND_INTERNAL_FUNCTION是内置的函数。也就是说PHP将内置的函数和
用户定义的函数分别保存。
1.用户函数(ZEND_USER_FUNCTION)
用户自定义函数是非常常用的函数种类,如下面的代码,定义了一个用户自定义的函数:
function tipi( $name ){
$return = &Hi! & . $name;
return $return;
这个示例中,对自定义函数传入了一个参数,并将其与Hi! 一起输出并做为返回值返回。
从这个例子可以看出函数的基本特点:运行时声明、可以传参数、有值返回。
当然,有些函数只是进行一些操作,并不一定显式的有返回值,在PHP的实现中,即使没有显式的返回,
Zend引擎也会“帮你“返回NULL。
可知,ZE在执行过程中,会将运行时信息存储于_zend_execute_data中:
struct _zend_execute_data {
//...省略部分代码
zend_function_state function_state;
zend_function *fbc; /* Function Being Called */
//...省略部分代码
在程序初始化的过程中,function_state也会进行初始化,function_state由两个部分组成:
typedef struct _zend_function_state {
zend_function *function;
void **arguments;
} zend_function_state;
**arguments是一个指向函数参数的指针,而函数体本身则存储于*function中, *function是一个zend_function结构体,
它最终存储了用户自定义函数的一切信息,它的具体结构是这样的:
typedef union _zend_function {
zend_uchar type;
/* 如用户自定义则为 #define ZEND_USER_FUNCTION 2
MUST be the first element of this struct! */
struct {
zend_uchar type;
/* never used */
char *function_name;
//函数名称
zend_class_entry *scope; //函数所在的类作用域
zend_uint fn_flags;
// 作为方法时的访问类型等,如ZEND_ACC_STATIC等
union _zend_function *prototype; //函数原型
zend_uint num_args;
//参数数目
zend_uint required_num_args; //需要的参数数目
zend_arg_info *arg_info;
//参数信息指针
zend_bool pass_rest_by_reference;
unsigned char return_reference;
} common;
zend_op_array op_array;
//函数中的操作
zend_internal_function internal_function;
} zend_function;
zend_function的结构中的op_array存储了该函数中所有的操作,当函数被调用时,ZE就会将这个op_array中的opline一条条顺次执行,
并将最后的返回值返回。
从VLD扩展中查看的关于函数的信息可以看出,函数的定义和执行是分开的,一个函数可以作为一个独立的运行单元而存在。
2.内部函数(ZEND_INTERNAL_FUNCTION)
ZEND_INTERNAL_FUNCTION函数是由扩展、PHP内核、Zend引擎提供的内部函数,一般用“C/C++”编写,可以直接在用户脚本中调用的函数。如下为内部函数的结构:
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
char * function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
struct _zend_module_entry *module;
} zend_internal_function;
最常见的操作是在模块初始化时,ZE会遍历每个载入的扩展模块,然后将模块中function_entry中指明的每一个函数(module->functions),
创建一个zend_internal_function结构, 并将其type设置为ZEND_INTERNAL_FUNCTION,将这个结构填入全局的函数表(HashTable结构);
函数设置及注册过程见 Zend/zend_API.c文件中的 zend_register_functions函数。这个函数除了处理函数,也处理类的方法,包括那些魔术方法。
内部函数的结构与用户自定义的函数结构基本类似,有一些不同,
调用方法,handler字段. 如果是ZEND_INTERNAL_FUNCTION, 那么ZE就调用zend_execute_internal,通过zend_internal_function.handler来执行这个函数。
而用户自定义的函数需要生成中间代码,然后通过中间代码映射到相对就把方法调用。
内置函数在结构中多了一个module字段,表示属于哪个模块。不同的扩展其模块不同。
type字段,在用户自定义的函数中,type字段几乎无用,而内置函数中的type字段作为几种内部函数的区分。
3.变量函数
PHP 支持变量函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且将尝试执行它。
除此之外,这个可以被用于实现回调函数,函数表等。
对比使用变量函数和内部函数的调用:
变量函数$func
$func = 'print_r';
$func('i am print_r function.');
通过VLD来查看这段代码编译后的中间代码:
function name:
(null)
number of ops:
compiled vars:
!0 = $func
--------------------------------------------------------------------------------
!0, 'print_r'
INIT_FCALL_BY_NAME
EXT_FCALL_BEGIN
'i+am+print_r+function.'
DO_FCALL_BY_NAME
EXT_FCALL_END
内部函数print_r
('i am print_r function.');
通过VLD来查看这段代码编译后的中间代码:
function name:
(null)
number of ops:
compiled vars:
--------------------------------------------------------------------------------
EXT_FCALL_BEGIN
'i+am+print_r+function.'
EXT_FCALL_END
对比发现,二者在调用的中间代码上存在一些区别。变量函数是DO_FCALL_BY_NAME,而内部函数是DO_FCALL。
这在语法解析时就已经决定了,
见Zend/zend_complie.c文件的zend_do_end_function_call函数中部分代码:
if (!is_method && !is_dynamic_fcall && function_name-&op_type==IS_CONST) {
opline-&opcode = ZEND_DO_FCALL;
opline-&op1 = *function_name;
ZVAL_LONG(&opline-&op2.u.constant, zend_hash_func(Z_STRVAL(function_name-&u.constant), Z_STRLEN(function_name-&u.constant) + 1));
} else {
opline-&opcode = ZEND_DO_FCALL_BY_NAME;
SET_UNUSED(opline-&op1);
如果不是方法,并且不是动态调用,并且函数名为字符串常量,则其生成的中间代码为ZEND_DO_FCALL。其它情况则为ZEND_DO_FCALL_BY_NAME。
另外将变量函数作为回调函数,其处理过程在Zend/zend_complie.c文件的zend_do_pass_param函数中。
最终会体现在中间代码执行过程中的 ZEND_SEND_VAL_SPEC_CONST_HANDLER 等函数中。
4.匿名函数
匿名函数是一类不需要指定表示符,而又可以被调用的函数或子例程,匿名函数可以方便的作为参数传递给其他函数,
关于匿名函数的详细信息请阅读
函数间的转换
在函数调用的执行代码中我们会看到这样一些强制转换:
EX(function_state).function = (zend_function *) op_array;
EG(active_op_array) = (zend_op_array *) EX(function_state).function;
这些不同结构间的强制转换是如何进行的呢?
首先我们来看zend_function的结构,在Zend/zend_compile.h文件中,其定义如下:
typedef union _zend_function {
zend_uchar type;
/* MUST be the first element of this struct! */
struct {
zend_uchar type;
/* never used */
char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
} common;
zend_op_array op_array;
zend_internal_function internal_function;
} zend_function;
这是一个联合体,我们来温习一下联合体的一些特性。
联合体的所有成员变量共享内存中的一块内存,在某个时刻只能有一个成员使用这块内存,
并且当使用某一个成员时,其仅能按照它的类型和内存大小修改对应的内存空间。
我们来看看一个例子:
#include &stdio.h&
#include &stdlib.h&
int main() {
union _utype
char ch[2];
} utype;
a.ch[0] = '1';
a.ch[1] = '1';
(&a.i= %d a.ch=%s&,a.i, a.ch);
getchar();
return (EXIT_SUCCESS);
程序输出:a.i= 12593 a.ch=11
当修改ch的值时,它会依据自己的规则覆盖i字段对应的内存空间。
'1'对应的ASCII码值是49,二进制为,当ch字段的两个元素都为'1'时,此时内存中存储的二进制为 10001
转成十进制,其值为12593。
回过头来看zend_function的结构,它也是一个联合体,第一个字段为type,
在common中第一个字段也为type,并且其后面注释为/* Never used*/,此处的type字段的作用就是为第一个字段的type留下内存空间。并且不让其它字段干扰了第一个字段。
我们再看zend_op_array的结构:
struct _zend_op_array {
/* Common elements */
zend_uchar type;
char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
zend_bool done_pass_two;
这里的字段集和common的一样,于是在将zend_function转化成zend_op_array时并不会产生影响,这种转变是双向的。
再看zend_internal_function的结构:
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
char * function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
struct _zend_module_entry *module;
} zend_internal_function;
同样存在公共元素,和common结构体一样,我们可以将zend_function结构强制转化成zend_internal_function结构,并且这种转变是双向的。
总的来说zend_internal_function,zend_function,zend_op_array这三种结构在一定程序上存在公共的元素,
于是这些元素以联合体的形式共享内存,并且在执行过程中对于一个函数,这三种结构对应的字段在值上都是一样的,
于是可以在一些结构间发生完美的强制类型转换。
可以转换的列表如下:
zend_function可以与zend_op_array互换
zend_function可以与zend_internal_function互换
但是一个zend_op_array结构转换成zend_function是不能再次转变成zend_internal_function结构的,反之亦然。
其实zend_function就是一个混合的数据结构,这种结构在一定程序上节省了内存空间。
第二节 函数的定义,传参及返回值
在本章开头部分,介绍了四种函数,而在本小节,我们从第一种函数:用户自定义的函数开始来认识函数。
本小节包括函数的定义,函数的参数传递和函数的返回值三个部分。下面我们将对每个部分做详细介绍。
函数的定义
在PHP中,用户函数的定义从function关键字开始。如下所示简单示例:
function foo($var) {
这是一个非常简单的函数,它所实现的功能是定义一个函数,函数有一个参数,函数的内容是在标准输出端输出传递给它的参数变量的值。
函数的一切从function开始。我们从function开始函数定义的探索之旅。
在 Zend/zend_language_scanner.l中我们找到如下所示的代码:
&ST_IN_SCRIPTING&&function& {
return T_FUNCTION;
它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,我们开始语法分析。
在 Zend/zend_language_parser.y文件中找到函数的声明过程标记如下:
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() {
function t() {
执行代码,可以看到屏幕上输出如下报错信息:
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
函数的参数
前一小节介绍了函数的定义,函数的定义是一个将函数名注册到函数列表的过程,在了解了函数的定义后,我们来看看函数的参数。
这一小节将包括用户自定义函数的参数、内部函数的参数和参数的传递:
用户自定义函数的参数
在中,我们对于参数的类型提示做了分析,这里我们在这一小节的基础上,进行一些更详细的说明。
在经过词语分析,语法分析后,我们知道对于函数的参数检查是通过 zend_do_receive_arg 函数来实现的。在此函数中对于参数的关键代码如下:
CG(active_op_array)-&arg_info = erealloc(CG(active_op_array)-&arg_info,
(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..value.str.val,
varname-&u..value.str.len);
cur_arg_info-&name_len = varname-&u..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_arg×× 时都会加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;
function_state等结构请参照本章第一小节。
在了解func_num_args函数的实现后,func_get_args函数的实现过程就简单了,它们的数据源是一样的,
只是前面返回的是长度,而这里返回了一个创建的数组。数组中存放的是从ex->function_state.arguments转化后的数据。
内部函数的参数
以上我们所说的都是用户自定义函数中对于参数的相关内容。下面我们开始讲解内部函数是如何传递参数的。
以常见的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) {
... //省略
这包括了两个操作:一个是取参数的个数,一个是解析参数列表。
取参数的个数
取参数的个数是通过ZEND_NUM_ARGS()宏来实现的。其定义如下:
#define ZEND_NUM_ARGS()
PHP3 中使用的是宏 ARG_COUNT
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
解析参数列表
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() 在解析参数的同时会尽可能地转换参数类型,这样就可以确保我们总是能得到所期望的类型的变量。
任何一种标量类型都可以转换为另外一种标量类型,但是不能在标量类型与复杂类型(比如数组、对象和资源等)之间进行转换。
如果成功地解析和接收到了参数并且在转换期间也没出现错误,那么这个函数就会返回 SUCCESS,否则返回 FAILURE。
如果这个函数不能接收到所预期的参数个数或者不能成功转换参数类型时就会抛出一些错误信息。
第三个参数指定的各个参数类型列表如下所示:
l - 长整形
d - 双精度浮点类型
s - 字符串 (也可能是空字节)和其长度
b - 布尔型
r - 资源,保存在 zval*
a - 数组,保存在 zval*
o - (任何类的)对象,保存在 zval *
O - (由class entry 指定的类的)对象,保存在 zval *
z - 实际的 zval*
除了各个参数类型,第三个参数还可以包含下面一些字符,它们的含义如下:
| - 表明剩下的参数都是可选参数。如果用户没有传进来这些参数值,那么这些值就会被初始化成默认值。
/ - 表明参数解析函数将会对剩下的参数以 SEPARATE_ZVAL_IF_NOT_REF() 的方式来提供这个参数的一份拷贝,除非这些参数是一个引用。
! - 表明剩下的参数允许被设定为 NULL(仅用在 a、o、O、r和z身上)。如果用户传进来了一个 NULL 值,则存储该参数的变量将会设置为 NULL。
参数的传递
在PHP的运行过程中,如果函数有参数,当执行参数传递时,所传递参数的引用计数会发生变化。
如和Xdebug的作者在其文章
中的示例的类似代码:
function do_something($s) {
xdebug_debug_zval('s');
return $s;
$a = 1111;
$b = do_something($a);
如果你安装了xdebug,此时会输出s变量的refcount为3,如果使用debug_zval_dump,会输出4。
因为此内部函数调用也对refcount执行了加1操作。
这里的三个引用计数分别是:
function stack中的引用
function symbol table中引用
原变量$a的引用。
这个函数符号表只有用户定义的函数才需要,内置和扩展里的函数不需要此符号表。
debug_zval_dump()是内置函数,并不需要符号表,所以只增加了1。
xdebug_debug_zval()传递的是变量名字符串,所以没有增加refcount。
每个PHP脚本都有自己专属的全局符号表,而每个用户自定义的函数也有自己的符号表,
这个符号表用来存储在这个函数作用域下的属于它自己的变量。当调用每个用户自定义的函数时,
都会为这个函数创建一个符号表,当这个函数返回时都会释放这个符号表。
当执行一个拥有参数的用户自定义的函数时,其实它相当于赋值一个操作,即$s = $a;
只是这个赋值操作的引用计数会执行两次,除了给函数自定义的符号表,还有一个是给函数栈。
参数的传递的第一步是SEND_VAR操作,这一步操作是在函数调用这一层级,如示例的PHP代码通过VLD生成的中间代码:
compiled vars:
!0 = $a, !1 = $b
--------------------------------------------------------------------------------
EXT_FCALL_BEGIN
EXT_FCALL_END
path #1: 0,
Function demo:
函数调用是DO_FCALL,在此中间代码之前有一个SEND_VAR操作,此操作的作用是将实参传递给函数,
并且将它添加到函数栈中。最终调用的具体代码参见zend_send_by_var_helper_SPEC_CV函数,
在此函数中执行了引用计数加1(Z_ADDREF_P)操作和函数栈入栈操作(zend_vm_stack_push)。
与第一步的SEND操作对应,第二步是RECV操作。
RECV操作和SEND_VAR操作不同,它是归属于当前函数的操作,仅为此函数服务。
它的作用是接收SEND过来的变量,并将它们添加到当前函数的符号表。示例函数生成的中间代码如下:
compiled vars:
--------------------------------------------------------------------------------
参数和普通局部变量一样 ,都需要进行操作,都需要保存在符号表(或CVs里,不过查找一般都是直接从变量变量数组里查找的)。
如果函数只是需要读这个变量,如果我们将这个变量复制一份给当前函数使用的话,在内存使用和性能方面都会有问题,而现在的方案却避免了这个问题,
如我们的示例:使用类似于赋值的操作,将原变量的引用计数加一,将有变化时才将原变量引用计数减一,并新建变量。
其最终调用是ZEND_RECV_SPEC_HANDLER。
参数的压栈操作用户自定义的函数和内置函数都需要,而RECV操作仅用户自定义函数需要。
函数的返回值
在编程语言中,一个函数或一个方法一般都有返回值,但也存在不返回值的情况,此时,这些函数仅仅是处理一些事务,
没有返回,或者说没有明确的返回值,在pascal语言中它有一个专有的关键字 procedure 。
在PHP中,函数都有返回值,分两种情况,使用return语句明确的返回和没有return语句返回NULL。
return语句
当使用return语句时,PHP给用户自定义的函数返回指定类型的变量。
依旧我们查看源码的方式,对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) {
返回引用时不允许常量和临时变量
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)) { //
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; //
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)。ZE内核对值返回和引用返回作了区分,
并且在此基础上对常量,临时变量和其它类型的变量在返回时进行了不同的处理。在return执行完之前,
ZE内核通过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。
这也是ZE内核自动给函数加上NULL返回的原因之一。
没有return语句的函数
在PHP中,没有过程这个概念,只有没有返回值的函数。但是对于没有返回值的函数,PHP内核会“帮你“加上一个NULL来做为返回值。
这个“帮你”的操作也是在生成中间代码时进行的。在每个函数解析时都需要执行函数 zend_do_end_function_declaration,
在此函数中有一条语句:
zend_do_return(NULL, 0 TSRMLS_CC);
结合前面的内容,我们知道这条语句的作用就是返回NULL。这就是没有return语句的函数返回NULL的原因所在。
内部函数的返回值
内部函数的返回值都是通过一个名为 return_value 的变量传递的。
这个变量同时也是函数中的一个参数,在PHP_FUNCTION函数扩展开来后可以看到。
这个参数总是包含有一个事先申请好空间的 zval 容器,因此你可以直接访问其成员并对其进行修改而无需先对 return_value 执行一下 MAKE_STD_ZVAL 宏指令。
为了能够更方便从函数中返回结果,也为了省却直接访问 zval 容器内部结构的麻烦,ZEND 提供了一大套宏命令来完成相关的这些操作。
这些宏命令会自动设置好类型和数值。
从函数直接返回值的宏:
RETURN_RESOURCE(resource) 返回一个资源。
RETURN_BOOL(bool) 返回一个布尔值。
RETURN_NULL() 返回一个空值。
RETURN_LONG(long) 返回一个长整数。
RETURN_DOUBLE(double) 返回一个双精度浮点数。
RETURN_STRING(string, duplicate)
返回一个字符串。duplicate 表示这个字符是否使用 estrdup() 进行复制。
RETURN_STRINGL(string, length, duplicate) 返回一个定长的字符串。其余跟 RETURN_STRING 相同。这个宏速度更快而且是二进制安全的。
RETURN_EMPTY_STRING() 返回一个空字符串。
RETURN_FALSE
返回一个布尔值假。
RETURN_TRUE
返回一个布尔值真。
设置函数返回值的宏:
RETVAL_RESOURCE(resource) 设定返回值为指定的一个资源。
RETVAL_BOOL(bool) 设定返回值为指定的一个布尔值。
RETVAL_NULL
设定返回值为空值
RETVAL_LONG(long) 设定返回值为指定的一个长整数。
RETVAL_DOUBLE(double) 设定返回值为指定的一个双精度浮点数。
RETVAL_STRING(string, duplicate)
设定返回值为指定的一个字符串,duplicate 含义同 RETURN_STRING。
RETVAL_STRINGL(string, length, duplicate) 设定返回值为指定的一个定长的字符串。其余跟 RETVAL_STRING 相同。这个宏速度更快而且是二进制安全的。
RETVAL_EMPTY_STRING
设定返回值为空字符串。
RETVAL_FALSE
设定返回值为布尔值假。
RETVAL_TRUE
设定返回值为布尔值真。
如果需要返回的是像数组和对象这样的复杂类型的数据,那就需要先调用 array_init() 和 object_init(),
也可以使用相应的 hash 函数直接操作 return_value。
由于这些类型主要是由一些杂七杂八的东西构成,所以对它们就没有了相应的宏。
关于内部函数的return_value值是如何赋值给*EG(return_value_ptr_ptr),
函数的调用是如何进行的,请阅读下一小节 .
第三节 函数的调用和执行
前面小节中对函数的内部表示以及参数的传递,返回值都有了介绍,那函数是怎么被调用的呢?
内置函数和用户定义函数在调用时会有什么不一样呢?下面将介绍函数调用和执行的过程。
函数的调用
函数被调用需要一些基本的信息,比如函数的名称,参数以及函数的定义(也就是函数的具体执行内容),
从我们开发者的角度来看,定义了一个函数我们在执行的时候自然知道这个函数叫什么名字,
以及调用的时候给传递了什么参数、函数的操作内容。但是对于Zend引擎不能像我们这样能“看懂”php源代码,
它需要对代码进行处理以后才能执行。我们还是从以下两个小例子开始:
function foo(){
&I'm foo!&;
foo();
下面我们先看一下其对应的opcodes:
function name:
(null)
---------------------------------------------------------------------------------
function name:
---------------------------------------------------------------------------------
'I%27m+foo%21'
上面是去除了一些枝节信息的的opcodes,可以看到执行时函数部分的opcodes是单独独立出来的,这点对于函数的执行特别重要,下面的部分会详细介绍。
现在,我们把焦点放到对foo函数的调用上面。调用foo的OPCODE是“DO_FCALL“, DO_FCALL进行函数调用操作时,ZE会在function_table中根据函数名
(如前所述,这里的函数名经过str_tolower的处理,所以PHP的函数名大小写不敏感)查找函数的定义, 如果不存在,
则报出“Call to undefined function xxx()"的错误信息; 如果存在,就返回该函数zend_function结构指针,
然后通过function.type的值来判断函数是内部函数还是用户定义的函数,
调用zend_execute_internal(zend_internal_function.handler)或者直接 调用zend_execute来执行这个函数包含的zend_op_array。
函数的执行
细心的读者可能会注意到上面opcodes里函数被调用的时候以及函数定义那都有个"function name:",其实用户定义函数的执行与其他语句的执行并无区别,
在本质上看,其实函数中的php语句与函数外的php语句并无不同。函数体本身最大的区别,在于其执行环境的不同。
这个“执行环境”最重要的特征就是变量的作用域。大家都知道,函数内定义的变量在函数体外是无法直接使用的,反之也是一样。那么,在函数执行的时候,
进入函数前的环境信息是必须要保存的。在函数执行完毕后,这些环境信息也会被还原,使整个程序继续的执行下去。
内部函数的执行与用户函数不同。用户函数是php语句一条条“翻译”成op_line组成的一个op_array,而内部函数则是用C来实现的,因为执行环境也是C环境,
所以可以直接调用。如下面的例子:
$foo = 'test';
print_r($foo);
对应的opcodes也很简单:
---------------------------------------------------------------------------------
!0, 'test'
可以看出,生成的opcodes中,内部函数和用户函数的处理都是由DO_FCALL来进行的。而在其具体实现的zend_do_fcall_common_helper_SPEC()中,
则对是否为内部函数进行了判断,如果是内部函数,则使用一个比较长的调用
((zend_internal_function *) EX(function_state).function)-&handler(opline-&extended_value, EX_T(opline-&result.u.var).var.ptr, EX(function_state).function-&common
.return_reference?&EX_T(opline-&result.u.var).var.ptr:NULL, EX(object), RETURN_VALUE_USED(opline) TSRMLS_CC);
上面这种方式的内部函数是在zend_execute_internal函数没有定义的情况下。而在而在Zend/zend.c文件的zend_startup函数中,
zend_execute_internal = NULL;
此函数确实被赋值为NULL。于是我们在if (!zend_execute_internal)判断时会成立,所以我们是执行那段很长的调用。
那么,这段很长的调用到底是什么呢?以我们常用的 count函数为例。在中,
我们知道内部函数所在的结构体中
有一个handler指针指向此函数需要调用的内部定义的C函数。
这些内部函数在模块初始化时就以扩展的函数的形式加载到EG(function_table)。其调用顺序:
php_module_startup --& php_register_extensions --& zend_register_internal_module
--& zend_register_module_ex --& zend_register_functions
zend_register_functions(NULL, module-&functions, NULL, module-&type TSRMLS_CC)
在standard扩展中。module的定义为:
zend_module_entry basic_functions_module = {
STANDARD_MODULE_HEADER_EX,
standard_deps,
&standard&,
/* extension name */
basic_functions,
/* function list */
... //省略
从上面的代码可以看出,module->functions是指向basic_functions。在basic_functions.c文件中查找basic_functions的定义。
const zend_function_entry basic_functions[] = {
PHP_FE(count,
arginfo_count)
#define PHP_FE
#define ZEND_FE(name, arg_info)
ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FN(name) zif_##name
#define ZEND_FENTRY(zend_name, name, arg_info, flags)
{ #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },
综合上面的代码,count函数最后调用的函数名为zif_count,但是此函数对外的函数名还是为count。
调用的函数名name以第二个元素存放在zend_function_entry结构体数组中。
对于zend_function_entry的结构
typedef struct _zend_function_entry {
const char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
const struct _zend_arg_info *arg_info;
zend_uint num_args;
zend_uint flags;
} zend_function_entry;
第二个元素为handler。这也就是我们在执行内部函数时的调用方法。因此在执行时就会调用到对应的函数。
对于用户定义的函数,在zend_do_fcall_common_helper_SPEC()函数中,
if (EX(function_state).function-&type == ZEND_USER_FUNCTION ||
EX(function_state).function-&common.scope) {
should_change_scope = 1;
EX(current_this) = EG(This);
EX(current_scope) = EG(scope);
EX(current_called_scope) = EG(called_scope);
EG(This) = EX(object);
EG(scope) = (EX(function_state).function-&type == ZEND_USER_FUNCTION || !EX(object)) ? EX(function_state).function-&common.scope : NULL;
EG(called_scope) = EX(called_scope);
先将EG下的This,scope等暂时缓存起来(这些在后面会都恢复到此时缓存的数据)。在此之后,对于用户自定义的函数,
程序会依据zend_execute是否等于execute并且是否为异常来判断是返回,还是直接执行函数定义的op_array:
if (zend_execute == execute && !EG(exception)) {
EX(call_opline) = opline;
ZEND_VM_ENTER();
} else {
zend_execute(EG(active_op_array) TSRMLS_CC);
而在Zend/zend.c文件的zend_startup函数中,已将zend_execute赋值为:
zend_execute = execute;
从而对于异常,程序会抛出异常;其它情况,程序会调用execute执行此函数中生成的opcodes。
execute函数会遍历所传递给它的zend_op_array数组,以方式
ret = EX(opline)-&handler(execute_data TSRMLS_CC)
调用每个opcode的处理函数。而execute_data在execute函数开始时就已经给其分配了空间,这就是这个函数的执行环境。
第四节 匿名函数及闭包
匿名函数在编程语言中出现的比较早,最早出现在Lisp语言中,随后很多的编程语言都开始有这个功能了,
目前使用比较广泛的Javascript以及C#,PHP直到5.3才开始真正支持匿名函数,
C++的新标准也开始支持了。
匿名函数是一类不需要指定标示符,而又可以被调用的函数或子例程,匿名函数可以方便的作为参数传递给其他函数,
最常见应用是作为回调函数。
闭包(Closure)
说到匿名函数,就不得不提到闭包了,闭包是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数,
这个被引用的自由变量将和这个函数一同存在,即使离开了创建它的环境也一样,所以闭包也可认为是有函数和与其相关引用组合而成的实体。
在一些语言中,在函数内定义另一个函数的时候,如果内部函数引用到外部函数的变量,则可能产生闭包。在运行外部函数时,
一个闭包就形成了。
这个词和匿名函数很容易被混用,其实这是两个不同的概念,这可能是因为很多语言实现匿名函数的时候允许形成闭包。
使用create_function()创建"匿名"函数
前面提到PHP5.3中才才开始正式支持匿名函数,说到这里可能会有细心读者有意见了,因为有个函数是可以生成匿名函数的: create_function函数,
在手册里可以查到这个在PHP4.1和PHP5中就有了,这个函数通常也能作为匿名回调函数使用,
$array = (1, 2, 3, 4);
($array, ('$value', 'echo $'));
这段代码只是将数组中的值依次输出,当然也能做更多的事情。 那为什么这不算真正的匿名函数呢,
我们先看看这个函数的返回值,这个函数返回一个字符串,
通常我们可以像下面这样调用一个函数:
function a() {
'function a';
$a();
我们在实现回调函数的时候也可以采用这样的方式,例如:
function do_something($callback) {
$callback();
这样就能实现在函数do_something()执行完成之后调用$callback指定的函数。回到create_function函数的返回值:
函数返回一个唯一的字符串函数名,出现错误的话则返回FALSE。这么说这个函数也只是动态的创建了一个函数,
而这个函数是有函数名的,也就是说,其实这并不是匿名的。只是创建了一个全局唯一的函数而已。
$func = ('', 'echo &Function created dynamic&;');
$func; // lambda_1
$func();
// Function created dynamic
$my_func = 'lambda_1';
$my_func(); // 不存在这个函数
lambda_1(); // 不存在这个函数
上面这段代码的前面很好理解,create_function就是这么用的,后面通过函数名来调用却失败了,这就有些不好理解了,
php是怎么保证这个函数是全局唯一的? lambda_1看起来也是一个很普通的函数名,如果我们先定义一个叫做lambda_1的函数呢?
这里函数的返回字符串会是lambda_2,它在创建函数的时候会检查是否这个函数是否存在知道找到合适的函数名,
但如果我们在create_function之后定义一个叫做lambda_1的函数会怎么样呢? 这样就出现函数重复定义的问题了,
这样的实现恐怕不是最好的方法,实际上如果你真的定义了名为lambda_1的函数也是不会出现我所说的问题的。这究竟是怎么回事呢?
上面代码的倒数2两行也说明了这个问题,实际上并没有定义名为lambda_1的函数。
也就是说我们的lambda_1和create_function返回的lambda_1并不是一样的!? 怎么会这样呢? 那只能说明我们没有看到实质,
只看到了表面,表面是我们在echo的时候输出了lambda_1,而我们的lambda_1是我们自己敲入的.
我们还是使用函数来看看吧。
$func = ('', 'echo &Hello&;');
$my_func_name = 'lambda_1';
($func);
// string(9) &lambda_1& refcount(2)
($my_func_name); // string(8) &lambda_1& refcount(2)
看出来了吧,他们的长度居然不一样,长度不一样也即是说不是同一个函数,所以我们调用的函数当然是不存在的,
我们还是直接看看create_function函数到底都做了些什么吧。
该实现见: $PHP_SRC/Zend/zend_builtin_functions.c
#define LAMBDA_TEMP_FUNCNAME
&__lambda_func&
ZEND_FUNCTION(create_function)
// ... 省去无关代码
function_name = (char *) emalloc(sizeof(&0lambda_&)+MAX_LENGTH_OF_LONG);
function_name[0] = '\0';
// &--- 这里
function_name_length = 1 + sprintf(function_name + 1, &lambda_%d&, ++EG(lambda_count));
} while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE);
zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME));
RETURN_STRINGL(function_name, function_name_length, 0);
该函数在定义了一个函数之后,给函数起了个名字,它将函数名的第一个字符变为了'\0'也就是空字符,
然后在函数表中查找是否已经定义了这个函数,如果已经有了则生成新的函数名, 第一个字符为空字符的定义方式比较特殊,
因为在用户代码中无法定义出这样的函数, 也就不存在命名冲突的问题了,这也算是种取巧(tricky)的做法,
在了解到这个特殊的函数之后,我们其实还是可以调用到这个函数的, 只要我们在函数名前加一个空字符就可以了,
chr()函数可以帮我们生成这样的字符串, 例如前面创建的函数可以通过如下的方式访问到:
$my_func = (0) . &lambda_1&;
$my_func(); // Hello
这种创建"匿名函数"的方式有一些缺点:
函数的定义是通过字符串动态eval的, 这就无法进行基本的语法检查;
这类函数和普通函数没有本质区别, 无法实现闭包的效果.
真正的匿名函数
在PHP5.3引入的众多功能中, 除了匿名函数还有一个特性值得讲讲:
__invoke魔幻方法
这个魔幻方法被调用的时机是: 当一个对象当做函数调用的时候, 如果对象定义了__invoke魔幻方法则这个函数会被调用,
这和C++中的操作符重载有些类似, 例如可以像下面这样使用:
class Callme {
public function __invoke($phone_num) {
&Hello: $phone_num&;
$call = new Callme();
$call(); // &Hello:
匿名函数的实现
前面介

我要回帖

更多关于 数据结构算法代码实现 的文章

 

随机推荐