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私房菜