文档章节

深入理解PHP原理之--echo的实现

哈狮子
 哈狮子
发布于 2014/09/30 15:57
字数 1265
阅读 65
收藏 1

晚上没事写了一篇文章:
PHP源代码分析-echo实现详解
原谅出处:http://jackywdx.cn/2009/01/implement_of_php_echo
echo,这个是PHP运用得最多的标记之一,算不上是函数,PHP手册里这么写的,因为它没有返回值。今天好奇就去看看PHP的源代码,因为echo不是一般的函数,所以找起来比较费劲,一般的函数只要搜索PHP_FUNCTION(fun_name)基本就能找着函数的实现方式,但是PHP是一门脚本语言,所以的符号都会先经过词法解析和语法解析阶段,这两个阶段是由lex&yacc实现的。对应的文件在php_source/Zend/目录下面的zend_language_parser.y及zend_language_scanner.l
首先看zend_language_scanner.l文件,1077行:
<ST_IN_SCRIPTING>“echo” {
return T_ECHO;
}
ZEND引擎在读取一个PHP文件之后会先进行词法分析,就是用lex扫描,把对应的PHP字符转换成相应的标记(也叫token),比如你echo$a;在碰到这句首先会匹配到echo,符合上面的规则,然后就返回一个T_ECHO标记,这个在后面的语法分析会用上,也就是在zend_language_parser.y文件中:
unticked_statement:
。。。。中间有省略
|        T_GLOBAL global_var_list ‘;’
|        T_STATIC static_var_list ‘;’
|        T_ECHO echo_expr_list ‘;’
|        T_INLINE_HTML                        { zend_do_echo(&$1 TSRMLS_CC); }
看到了T_ECHO,后面跟着echo_expr_list,再搜这个字符串,找到:
echo_expr_list:
echo_expr_list ‘,’ expr { zend_do_echo(&$3 TSRMLS_CC); }            //第1行,
|        expr                                        { zend_do_echo(&$1 TSRMLS_CC); }   //第2行
对于第1行就像 echo $var_1,$var_2,
执行动作就是zend_do_echo()函数,在Zend/目录下面搜索一下这个函数,就能知道这个函数是在zend_compile.c文件里面实现的:
void zend_do_echo(znode *arg TSRMLS_DC)
{
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);
        opline->opcode = ZEND_ECHO;
        opline->op1 = *arg;
        SET_UNUSED(opline->op2);
}

这个函数没有做什么真正的输出动作,只是把这个zend_op操作数的类型置为ZEND_ECHO,把要输出的内容赋给opline->op1 = *arg;
真正的输出动作是由ZEND引擎实现的,要知道所有的操作数都会被ZEND引擎执行。再搜索一下ZEND_ECHO,在zend_vm_def.h头文件里面找到它的定义:
ZEND_VM_HANDLER(40, ZEND_ECHO, CONST|TMP|VAR|CV, ANY)
{
zend_op *opline = EX(opline);
zend_free_op free_op1;
zval z_copy;
zval *z = GET_OP1_ZVAL_PTR(BP_VAR_R);
if (Z_TYPE_P(z) == IS_OBJECT && Z_OBJ_HT_P(z)->get_method != NULL &&
zend_std_cast_object_tostring(z, &z_copy, IS_STRING TSRMLS_CC) == SUCCESS) {
zend_print_variable(&z_copy);
zval_dtor(&z_copy);
} else {
zend_print_variable(z);
}
FREE_OP1();
ZEND_VM_NEXT_OPCODE();
}
看红色的两个代码段,如果遇到的变量是一个对象,就调用zend_std_cast_object_tostring把对象转化为字符串,然后再调用zend_print_variable()输出。
剩下的工作就是找出zend_print_variable的实现了。不过我发现这个函数还真的隐藏得非常深,经过了一层又一层的调用,最后给找了再来。
在/Zend/zend_variables.c下面实现了zend_print_variable函数:
ZEND_API int zend_print_variable(zval *var)
{
        return zend_print_zval(var, 0);
}

在/Zend/zend.c文件里面实现了zend_print_zval
ZEND_API int zend_print_zval(zval *expr, int indent)
{
        return zend_print_zval_ex(zend_write, expr, indent);
}
ZEND_API int zend_print_zval_ex(zend_write_func_t write_func, zval *expr, int indent)
{
        zval expr_copy;
        int use_copy;
        zend_make_printable_zval(expr, &expr_copy, &use_copy);
        if (use_copy) {
                expr = &expr_copy;
        }
        if (expr->value.str.len==0) { /* optimize away empty strings */
                if (use_copy) {
                        zval_dtor(expr);
                }
                return 0;
        }
        write_func(expr->value.str.val, expr->value.str.len);
        if (use_copy) {
                zval_dtor(expr);
        }
        return expr->value.str.len;
}

注意上面函数标红的三个部分,
ZEND_API int zend_print_zval_ex(zend_write_func_t write_func, zval*expr, intindent)第一个参数是一个函数指针(忘了是不是这样叫,不明白的可以百度一下),所以实际上最后调用的是zend_write(expr,indent);
zend_write也是一个函数指针,在/Zend/zend.c里面:
typedef int (*zend_write_func_t)(const char *str, uint str_length);
ZEND_API zend_write_func_t zend_write;
而zend_write的初始化是在zend_startup()函数里面,这是zend引擎启动的时候需要做的一些初始化工作,有下面一句:
zend_write = (zend_write_func_t) utility_functions->write_function;
然后是在/main/目录下面的main.c文件里面的php_module_startup函数调用了zend_startup()函数,就是说PHP作为模块启动的时候需要进行的一些初始化动作都在这里执行了,在这个函数里面调用了下面几句:


zuf.write_function = php_body_write_wrapper;
zuf.fopen_function = php_fopen_wrapper_for_zend;
zuf.message_handler = php_message_handler_for_zend;      
zuf.block_interruptions = sapi_module.block_interruptions;
zuf.unblock_interruptions = sapi_module.unblock_interruptions;
zuf.get_configuration_directive = php_get_configuration_directive_for_zend;
zuf.ticks_function = php_run_ticks;
zuf.on_timeout = php_on_timeout;
zuf.stream_open_function = php_stream_open_for_zend;
zuf.vspprintf_function = vspprintf;
zuf.getenv_function = sapi_getenv;
zend_startup(&zuf, NULL, 1);

zuf是一个zend_utility_functions结构体,注意上面红色的两句,这样就把php_body_write_wrapper函数传给了zuf.write_function,后面还有好几层包装,最后的实现是在/main/output.c文件里面实现的,是下面这个函数:

PHPAPI int php_default_output_func(const char *str, uint str_len TSRMLS_DC){
        fwrite(str, 1, str_len, stderr);
        return str_len;}

可见,php里面的echo最后实际上是通过调用C里面的fwrite函数实现的,只是包装了十几层,暂时想不通为什么要经过这么多层的包装,经过这么多层的调用,难怪PHP的性能没法跟C比了。

本文转载自:http://bbs.phpchina.com/thread-100825-1-1.html

哈狮子
粉丝 2
博文 18
码字总数 4651
作品 0
常州
私信 提问
深入理解PHP原理之变量作用域(Scope in PHP)

我前面的文章(深入理解PHP原理之变量(Variables inside PHP))介绍了PHP变量的内部表示,但是,问题是,这些内部表示是如何和用户脚本中的变量联系起来的呢?也就是说,如果我在脚本中写下: ...

botkenni
2016/10/11
17
0
深入理解PHP原理之变量作用域

作者:laruence(http://www.laruence.com/) 地址: http://www.laruence.com/2008/08/26/463.html PHP变量的内部表示是如何和用户脚本中的变量联系起来的呢?也就是说,如果我在脚本中写下: ...

晨曦之光
2012/03/09
202
0
php自学提升进阶路线

一、实战演练 熟悉语法&api -> 阅读zend、ci、wordpress源码 -> 用PHP独立开发项目 -> 深入了解PHP引擎实现细节、扩展编写、性能优化等 (1周)HTML+CSS HTML5+CSS3 (2-3天)留言板或记事本...

stone_
2016/07/11
294
0
深入理解PHP原理之foreach

foreach是PHP中很常用的一个用作数组循环的控制语句。 因为它的方便和易用,自然也就在后端隐藏着很复杂的具体实现方式(对用户透明) 今天,我们就来一起分析分析,foreach是如何实现数组(对...

botkenni
2016/10/09
60
0
【PHP7源码分析】PHP7语言的执行原理

我们常用的高级语言有很多种,比较出名的有CC++、Python、 PHP、Go、Pascal等。而这些语言根据运行的方式不同,大体分为两种:编译型语言和解释型语言。 其中,编译型语言包括CC++、Pascal、...

陈雷_顺风车
2018/08/16
0
0

没有更多内容

加载失败,请刷新页面

加载更多

浅谈梯子游戏三门打法技巧走势攻略教程(学会稳稳上岸)

摘要:那么下面我就给大家总结一下技巧和玩法!一、玩梯子的前期准备:1,最重要是zi金准备,提前做好ben金准备能保证自己的zi金链不断,稳中求胜。2,其次是止sun底线,风险控制一定要懂得舍弃,买彩...

V_6696089
30分钟前
7
0
今天发布 需求登记表,不戴眼镜开发的成果

今天是不戴眼镜调理第八天 之前带五百度眼睛0.5 现在是做了三次调理,通常,成人比青少年要慢些 明天正式学习 调理技术 最近不怎么开电脑做开发,发现时间多了许多 虽然想把开发缓一阵子,但...

蒋志祥
38分钟前
3
0
Linux 之docker部署,走过的那些坑

初次使用docker, 多有不足,这里把坑一点点的记下来 概念篇 docker 是什么? 太多文字不同,书读的少, 不懂大大道理; 在我的理解, 它就是个沙箱环境; 在linux中 ,独立进程, 有着自己的小世界 使...

莫库什勒
52分钟前
5
0
vue class绑定 组件

本文转载于:专业的前端网站➬vue class绑定 组件 当在一个自定义组件上使用 class 属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。 例如,如果你声明了这个...

前端老手
今天
4
0
exist和in

exist和in select ..from table where exist (子查询) ; select ..from table where 字段 in (子查询) ; 如果主查询的数据集大,则使用In,效率高。 如果子查询的数据集大,则使用exist,效率高...

潦草的犀牛
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部