PHP 内核中是如何实现 empty, isset 这些函数的 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zencodex
V2EX    PHP

PHP 内核中是如何实现 empty, isset 这些函数的

  •  
  •   zencodex 2015-11-10 10:49:49 +08:00 2318 次点击
    这是一个创建于 3673 天前的主题,其中的信息可能已经有所发展或是发生改变。

    通常的函数是通过 ZEND_FUNCTION(xxx) 这种宏定义来实现的,这个规范很好理解,也很容易读懂源码。

    但 empty(), isset()的处理比较特殊,类似的还有 echo, eval 等。

    准备工作

    用于查看 PHP opcode 的扩展 vld ,下载:

    http://pecl.php.net/package/vld

    PHP 源码,分支 => remotes/origin/PHP-5.6.14

    git clone http://git.php.net/repository/php-src.git -b PHP-5.6.14 

    PHP opcode 对应参考:

    http://php.net/manual/en/internals2.opcodes.php

    PHP 执行程序版本为 5.6.14 ,其他版本 opcode 可能会有细微差别。

    PHP 内核源码分析:

    http://www.php-internals.com/book/

    开始分析

    示例代码 vld.php :

    <?php $a = 0; empty($a); isset($a); 

    通过 vld 查看 opcode ,php -d vld.active=1 vld.php

    number of ops: 10 compiled vars: !0 = $a line #* E I O op fetch ext return operands ------------------------------------------------------------------------------------- 2 0 E > EXT_STMT 1 ASSIGN !0, 0 3 2 EXT_STMT 3 ISSET_ISEMPTY_VAR 293601280 ~1 !0 4 FREE ~1 4 5 EXT_STMT 6 ISSET_ISEMPTY_VAR 310378496 ~2 !0 7 FREE ~2 6 8 EXT_STMT 9 > RETURN 1 branch: # 0; line: 2- 6; sop: 0; eop: 9; out1: -2 

    opcode 中都出现了 ZEND_ISSET_ISEMPTY_VAR ,我们一步步分析。

    当执行 PHP 源码,会先进行语法分析, empty, isset 的 yacc 如下:

    vim Zend/zend_language_parser.y +1265

    1265 internal_functions_in_yacc: 1266 T_ISSET '(' isset_variables ')' { $$ = $3; } 1267 | T_EMPTY '(' variable ')' { zend_do_isset_or_isempty(ZEND_ISEMPTY, &$$, &$3 TSRMLS_CC); } 1275 1276 isset_variables: 1277 isset_variable { $$ = $1; } 1280 1281 isset_variable: 1282 variable { zend_do_isset_or_isempty(ZEND_ISSET, &$$, &$1 TSRMLS_CC); } 

    最终都执行了 zend_do_isset_or_isempty ,继续查找:

    git grep -in "zend_do_isset_or_isempty" Zend/zend_compile.c:6287:void zend_do_isset_or_isempty(int type, znode *result, znode *variable TSRMLS_DC) /* {:{:{ */ 

    vi Zend/zend_compile.c +6287

    6287 void zend_do_isset_or_isempty(int type, znode *result, znode *variable TSRMLS_DC) /* {{{ */ 6288 { 6289 zend_op *last_op; 6290 6291 zend_do_end_variable_parse(variable, BP_VAR_IS, 0 TSRMLS_CC); 6292 6293 if (zend_is_function_or_method_call(variable)) { 6294 if (type == ZEND_ISEMPTY) { 6295 /* empty(func()) can be transformed to !func() */ 6296 zend_do_unary_op(ZEND_BOOL_NOT, result, variable TSRMLS_CC); 6297 } else { 6298 zend_error_noreturn(E_COMPILE_ERROR, "Cannot use isset() on the result of a function call (you can use \"null !== func()\" instead)"); 6299 } 6300 6301 return; 6302 } 6303 6304 if (variable->op_type == IS_CV) { 6305 last_op = get_next_op(CG(active_op_array) TSRMLS_CC); 6306 last_op->opcode = ZEND_ISSET_ISEMPTY_VAR; 

    最后一行 6306 , ZEND_ISSET_ISEMPTY_VAR 这个 opcode 出来了, IS_CV 判断参数是否为变量。
    注意 zend_is_function_or_method_call(variable),当 isset(fun($a)),函数参数写法会报错, empty 在 5.5 版本开始支持函数参数,低版本不支持。

    opcode 是由 zend_execute 执行的,最终会对应处理函数的查找,这个是核心,请参阅:

    http://www.php-internals.com/book/?p=chapt02/02-03-03-from-opcode-to-handler

    opcode 对应处理函数的命名规律:

    ZEND_[opcode]_SPEC_(变量类型 1)_(变量类型 2)_HANDLER 

    变量类型 1 和变量类型 2 是可选的,如果同时存在,那就是左值和右值,归纳有下几类: VAR TMP CV UNUSED CONST 这样可以根据相关的执行场景来判定。

    所以 ZEND_ISSET_ISEMPTY_VAR 对应的 handler 如下:

    Zend/zend_vm_execute.h:44233: ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_CONST_HANDLER, Zend/zend_vm_execute.h:44235: ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_VAR_HANDLER, Zend/zend_vm_execute.h:44236: ZEND_ISSET_ISEMPTY_VAR_SPEC_CONST_UNUSED_HANDLER, Zend/zend_vm_execute.h:44238: ZEND_ISSET_ISEMPTY_VAR_SPEC_TMP_CONST_HANDLER, Zend/zend_vm_execute.h:44240: ZEND_ISSET_ISEMPTY_VAR_SPEC_TMP_VAR_HANDLER, Zend/zend_vm_execute.h:44241: ZEND_ISSET_ISEMPTY_VAR_SPEC_TMP_UNUSED_HANDLER, Zend/zend_vm_execute.h:44243: ZEND_ISSET_ISEMPTY_VAR_SPEC_VAR_CONST_HANDLER, Zend/zend_vm_execute.h:44245: ZEND_ISSET_ISEMPTY_VAR_SPEC_VAR_VAR_HANDLER, Zend/zend_vm_execute.h:44246: ZEND_ISSET_ISEMPTY_VAR_SPEC_VAR_UNUSED_HANDLER, Zend/zend_vm_execute.h:44253: ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_CONST_HANDLER, Zend/zend_vm_execute.h:44255: ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_VAR_HANDLER, Zend/zend_vm_execute.h:44256: ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_UNUSED_HANDLER, 

    我们看下 ZEND_ISSET_ISEMPTY_VAR_SPEC_CV_VAR_HANDLER 这个处理函数:
    vim Zend/zend_vm_execute.h +37946

    38013 if (opline->extended_value & ZEND_ISSET) { 38014 if (isset && Z_TYPE_PP(value) != IS_NULL) { 38015 ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 1); 38016 } else { 38017 ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 0); 38018 } 38019 } else /* if (opline->extended_value & ZEND_ISEMPTY) */ { 38020 if (!isset || !i_zend_is_true(*value)) { 38021 ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 1); 38022 } else { 38023 ZVAL_BOOL(&EX_T(opline->result.var).tmp_var, 0); 38024 } 

    上面的 if ... else 就是判断是 isset ,还是 empty ,然后做不同处理, Z_TYPE_PP, i_zend_is_true 不同判断。
    echo 等处理类似,自己按照流程具体去分析。关键是根据映射表找到对应的 handler 处理函数。

    了解这些处理流程后,相信会对 PHP 语句的性能分析更熟悉。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2570 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 22ms UTC 11:43 PVG 19:43 LAX 03:43 JFK 06:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86