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://jackywdx.cn/2009/01/implement_of_php_echo
- PHP技术文章
- PHP中session和cookie的区别
- php设计模式(一):简介及创建型模式
- php设计模式结构型模式
- Php设计模式(三):行为型模式
- 十款最出色的 PHP 安全开发库中文详细介绍
- 12个提问频率最高的PHP面试题
- PHP 语言需要避免的 10 大误区
- PHP 死锁问题分析
- 致PHP路上的“年轻人”
- PHP网站常见安全漏洞,及相应防范措施总结
- 各开源框架使用与设计总结(一)
- 数据库的本质、概念及其应用实践(二)
- PHP导出MySQL数据到Excel文件(fputcsv)
- PHP中14种排序算法评测
- 深入理解PHP原理之--echo的实现
- PHP性能分析相关的函数
- PHP 性能分析10则
- 10 位顶级 PHP 大师的开发原则
- 30条爆笑的程序员梗 PHP是最好的语言
- PHP底层的运行机制与原理
- PHP 性能分析与实验——性能的宏观分析
- PHP7 性能翻倍关键大揭露
- 鸟哥:写在PHP7发布之际一些话
- PHP与MySQL通讯那点事
- Php session内部执行流程的再次剖析
- 关于 PHP 中的 Class 的几点个人看法
- PHP Socket 编程过程详解
- PHP过往及现在及变革
- PHP吉祥物大象的由来
- PHP生成静态页面的方法
- 吊炸天的 PHP 7 ,你值得拥有!
- PHP开发中文件操作疑难问答
- MongoDB PHP Driver的连接处理解析
- PHP 杂谈《重构-改善既有代码的设计》之二 对象
- 在php中判断一个请求是ajax请求还是普通请求的方法
- 使用HAProxy、PHP、Redis和MySQL支撑10亿请求每周架构细节
- HTML、HTML5、XHTML、CSS、SQL、JavaScript、PHP、Web Services 是什么?
- 重构-改善既有代码的设计
- PHP场景中getshell防御思路分享
- 移动互联时代,你看看除了PHP你还会些什么
- 安卓系统上搭建本地php服务器环境
- PHP中常见的缓存技术!
- PHP里10个鲜为人知但却非常有用的函数
- 成为一名PHP专家其实并不难
- PHP 命令行?是的,您可以!
- PHP开发提高效率技巧
- PHP八大安全函数解析
- PHP实现四种基本排序算法
- PHP开发中的中文编码问题
- php.get.post
- php发送get、post请求的6种方法简明总结
- 中高级PHP开发者应该掌握哪些技术?
- 前端开发
- web前端知识体系大全
- 前端工程与性能优化(下)
- 前端工程与性能优化(上)
- 2016 年技术发展方向
- Web应用检查清单
- 如何成为一名优秀的web前端工程师
- 前端组件化开发实践
- 移动端H5页面高清多屏适配方案
- 2015前端框架何去何从
- 从前端看“百度迁徙”的技术实现(一)
- 从前端看“百度迁徙”的技术实现(二)
- 前端路上的旅行
- 大公司里怎样开发和部署前端代码?
- 5个经典的前端面试问题
- 前端工程师新手必读
- 手机淘宝前端的图片相关工作流程梳理
- 一个自动化的前端项目实现(附源码)
- 前端代码异常日志收集与监控
- 15年双11手淘前端技术总结 - H5性能最佳实践
- 深入理解javascript原型和闭包系列
- 一切都是对象
- 函数和对象的关系
- prototype原型
- 隐式原型
- instanceof
- 继承
- 原型的灵活性
- 简述【执行上下文】上
- 简述【执行上下文】下
- this
- 执行上下文栈
- 简介【作用域】
- 【作用域】和【上下文环境】
- 从【自由变量】到【作用域链】
- 闭包
- 完结
- 补充:上下文环境和作用域的关系
- Linux私房菜