加入收藏 | 设为首页 | 会员中心 | 我要投稿 PHP编程网 - 钦州站长网 (https://www.0777zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

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

发布时间:2022-10-04 12:39:24 所属栏目:PHP教程 来源:
导读:  PHP源代码分析-echo实现详解

  echo,这个是PHP运用得最多的标记之一,算不上是函数,PHP手册里这么写的,因为它没有返回值。今天好奇就去看看PHP的源代码,因为echo不是一般的函数,所以找起来比较费劲,
  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()函数里面php源代码,这是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编程网 - 钦州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!