1. 程式人生 > >深入理解PHP原理之--echo的實現

深入理解PHP原理之--echo的實現

PHP原始碼分析-echo實現詳解
原諒出處:http://jackywdx.cn/2009/01/implement_of_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行:
<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比了。