PHP源代碼分析-echo實(shí)現(xiàn)詳解
echo,這個(gè)是PHP運(yùn)用得最多的標(biāo)記之一,算不上是函數(shù),PHP手冊(cè)里這么寫的,因?yàn)樗鼪](méi)有返回值。今天好奇就去看看PHP的源代碼,因?yàn)閑cho不是一般的函數(shù),所以找起來(lái)比較費(fèi)勁,一般的函數(shù)只要搜索PHP_FUNCTION(fun_name)基本就能找著函數(shù)的實(shí)現(xiàn)方式,但是PHP是一門腳本語(yǔ)言,所以的符號(hào)都會(huì)先經(jīng)過(guò)詞法解析和語(yǔ)法解析階段,這兩個(gè)階段是由lex&yacc實(shí)現(xiàn)的。對(duì)應(yīng)的文件在php_source/Zend/目錄下面的zend_language_parser.y及zend_language_scanner.l
首先看zend_language_scanner.l文件,1077行:
<ST_IN_SCRIPTING>“echo” {
return T_ECHO;
}
ZEND引擎在讀取一個(gè)PHP文件之后會(huì)先進(jìn)行詞法分析,就是用lex掃描,把對(duì)應(yīng)的PHP字符轉(zhuǎn)換成相應(yīng)的標(biāo)記(也叫token),比如你echo$a;在碰到這句首先會(huì)匹配到echo,符合上面的規(guī)則,然后就返回一個(gè)T_ECHO標(biāo)記,這個(gè)在后面的語(yǔ)法分析會(huì)用上,也就是在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,再搜這個(gè)字符串,找到:
echo_expr_list:
echo_expr_list ‘,’ expr { zend_do_echo(&$3 TSRMLS_CC); } //第1行,
| expr { zend_do_echo(&$1 TSRMLS_CC); } //第2行
對(duì)于第1行就像 echo $var_1,$var_2,
執(zhí)行動(dòng)作就是zend_do_echo()函數(shù),在Zend/目錄下面搜索一下這個(gè)函數(shù),就能知道這個(gè)函數(shù)是在zend_compile.c文件里面實(shí)現(xiàn)的:
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);
}
這個(gè)函數(shù)沒(méi)有做什么真正的輸出動(dòng)作,只是把這個(gè)zend_op操作數(shù)的類型置為ZEND_ECHO,把要輸出的內(nèi)容賦給opline->op1 = *arg;
真正的輸出動(dòng)作是由ZEND引擎實(shí)現(xiàn)的,要知道所有的操作數(shù)都會(huì)被ZEND引擎執(zhí)行。再搜索一下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();
}
看紅色的兩個(gè)代碼段,如果遇到的變量是一個(gè)對(duì)象,就調(diào)用zend_std_cast_object_tostring把對(duì)象轉(zhuǎn)化為字符串,然后再調(diào)用zend_print_variable()輸出。
剩下的工作就是找出zend_print_variable的實(shí)現(xiàn)了。不過(guò)我發(fā)現(xiàn)這個(gè)函數(shù)還真的隱藏得非常深,經(jīng)過(guò)了一層又一層的調(diào)用,最后給找了再來(lái)。
在/Zend/zend_variables.c下面實(shí)現(xiàn)了zend_print_variable函數(shù):
ZEND_API int zend_print_variable(zval *var)
{
return zend_print_zval(var, 0);
}
在/Zend/zend.c文件里面實(shí)現(xiàn)了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;
}
注意上面函數(shù)標(biāo)紅的三個(gè)部分,
ZEND_API int zend_print_zval_ex(zend_write_func_t write_func, zval*expr, intindent)第一個(gè)參數(shù)是一個(gè)函數(shù)指針(忘了是不是這樣叫,不明白的可以百度一下),所以實(shí)際上最后調(diào)用的是zend_write(expr,indent);
zend_write也是一個(gè)函數(shù)指針,在/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()函數(shù)里面,這是zend引擎啟動(dòng)的時(shí)候需要做的一些初始化工作,有下面一句:
zend_write = (zend_write_func_t) utility_functions->write_function;
然后是在/main/目錄下面的main.c文件里面的php_module_startup函數(shù)調(diào)用了zend_startup()函數(shù),就是說(shuō)PHP作為模塊啟動(dòng)的時(shí)候需要進(jìn)行的一些初始化動(dòng)作都在這里執(zhí)行了,在這個(gè)函數(shù)里面調(diào)用了下面幾句:
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是一個(gè)zend_utility_functions結(jié)構(gòu)體,注意上面紅色的兩句,這樣就把php_body_write_wrapper函數(shù)傳給了zuf.write_function,后面還有好幾層包裝,最后的實(shí)現(xiàn)是在/main/output.c文件里面實(shí)現(xiàn)的,是下面這個(gè)函數(shù):
PHPAPI int php_default_output_func(const char *str, uint str_len TSRMLS_DC){
fwrite(str, 1, str_len, stderr);
return str_len;
}
可見(jiàn),php里面的echo最后實(shí)際上是通過(guò)調(diào)用C里面的fwrite函數(shù)實(shí)現(xiàn)的,只是包裝了十幾層,暫時(shí)想不通為什么要經(jīng)過(guò)這么多層的包裝,經(jīng)過(guò)這么多層的調(diào)用,難怪PHP的性能沒(méi)法跟C比了。