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行:

“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比了。

php输出源代码,PHP源代码分析-echo实现详解相关推荐

  1. php output详解,PHP输出缓冲控制Output Control系列函数详解,output函数详解

    PHP输出缓冲控制Output Control系列函数详解,output函数详解 概述 以前研究过PHP的输入输出缓冲,不过博客搬家以后,原来文章找不到了,今天看到一篇好文,顺便转载过来. 简介 说到 ...

  2. Android Telephony分析(三) ---- RILJ详解

    前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程.  这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...

  3. mysql ssd 性能测试 写入_MySQL服务器的SSD性能问题分析和测试详解

    [问题] 我们有台HP的服务器,SSD在写IOPS约5000时,%util达到80%以上,那么这块SSD的性能究竟有没有问题,为解决这个问题做了下面测试. [工具] blktrace是linux下用来 ...

  4. java 生成dump_java dump文件怎么生成和分析-JMAP用法详解

    jmap是java自带的工具 1. 查看整个JVM内存状态 jmap -heap [pid] 2. 查看JVM堆中对象详细占用情况 jmap -histo [pid] 3. 导出整个JVM 中内存信息 ...

  5. Android Telephony分析(五) ---- TelephonyRegistry详解

    本文紧接着上一篇文章<Android Telephony分析(四) -- TelephonyManager详解 >的1.4小节.  从TelephonyRegistry的大部分方法中:  ...

  6. Android Telephony分析(二) ---- RegistrantList详解

    前言 本文主要讲解RegistrantList的原理,以及如何快速分析RegistrantList相关的代码流程.  在Telephony模块中,在RIL.Tracker(ServiceStateTr ...

  7. 一文数学数模-相关性分析(二)斯皮尔曼相关(spearman)相关性分析一文详解+python实例代码

    前言 相关性分析算是很多算法以及建模的基础知识之一了,十分经典.关于许多特征关联关系以及相关趋势都可以利用相关性分析计算表达.其中常见的相关性系数就有三种:person相关系数,spearman相关系 ...

  8. CTF---basecrack---Base编码分析工具安装详解

    CTF-basecrack-Base编码分析工具安装详解 准备工具:Python环境(最新版) 安装详情: 1)首先下载工具,访问网站或者私信我进行工具获取,网站地址:https://github.c ...

  9. 内核启动流程分析(二)配置详解

    总体概述 配置详解 配置的最终目的,是生成了.config文件,查看下这个文件, # # Automatically generated make config: don't edit # Linux ...

最新文章

  1. qt信号发送间隔短而槽耗时多_Qt信号槽问题汇总 - osc_9q1dp3jk的个人空间 - OSCHINA - 中文开源技术交流社区...
  2. Python3 中实现MATLAB中的点乘 即两列表对应元素相乘
  3. ceph对象存储折腾记
  4. 常见Web技术之间的关系,你了解多少?
  5. Oracle Redefine table online will clone and exchange source and intermedia table - 3
  6. WSGI、flup、fastcgi、web.py的关系
  7. MySQL日期时间转换函数
  8. 官网链接下载QT5 Creator
  9. icem二维非结构网格划分_Ansys Icem CFD网格划分实例详解PDF及附件
  10. QT入门之布局 水平布局、垂直布局、表单布局、网格布局
  11. Codeforces Round #476 (Div. 2) 题解
  12. c语言中u8,u16,u32和int区别
  13. 半加器设计(结构描述法)
  14. PayPal集成标准版案例(asp.net)关键源码
  15. 常用的Python3关键词提取方法
  16. 零基础想要快速的学好3D游戏建模,兼职接单私活,来看业内人士的分析
  17. 网卡的功能、构造与分类 | 什么是网卡?
  18. H3C链路二层聚合教程
  19. w ndows10故障如何重新进入,天正建筑T20常见问题
  20. 如何解除计算机上的安全警报,Win7安全警报怎么关闭?Win7关闭安全警报的方法...

热门文章

  1. HR 开发技术(abap 转载)
  2. java返回当年的天数_获取Java中当年剩余的天数
  3. win7系统安装信息服务器不可用怎么办,RPC服务器不可用?Win7系统RPC服务器不可用怎么办...
  4. bzoj 2330 / AcWing 368 银河 差分约束系统+tarjan缩点+拓扑排序
  5. RFID图书馆和传统图书馆的区别
  6. 二十五、非谓语动词_作主语
  7. 神兽传说1 java_神兽传说RPG
  8. Java 定义一个抽象类—水果,其中包括getWeight()方法,创建若干水果对象存放在一个水果类型的数组中,输出数组中所有水果的类型、重量。
  9. 喜闻乐见的 2048 游戏 (附源码)
  10. arm 各种 gcc 编译器区别