群友抛出一个题,空数组array()与0 比较大小的结果如何?
另一个群友也借机抛出了另一个问题
<?php var_dump(null == 0); var_dump(null == array()); var_dump(0 == array());
答案也有些匪夷所思,第一个问题的结果是,array()>0 第二个问题结果是 true,true,false
实验得到结果后的第一反应就是查文档,于是在php的官方文档中找到如下内容:
PHP: 比较运算符 - Manual
PHP: PHP 类型比较表 - Manual
在这里赞一下PHP的文档,描述很详实,demo也有代表性,相信这在语言的推广上起了不少作用。
问题似乎是解决了,但我还是不满足,颇有点知其然不知其所以然的感觉,于是产生了后面的内容。
第一个比较初级的想法就是查看opcode,在这里简单提一下opcode。引用官方文档的一段话:
When parsing PHP files, Zend Engine 2 generates a series of operation codes, commonly known as "opcodes", representing the function of the code.
大意是:Zend引擎在解析PHP文件时,会产生一组操作码,被称作“opcode”,用来表示代码中函数。我觉得描述基本如此,这里就不展开讲了。
于是做了如下实验:
1.建立test文件,内容如下:
<?php var_dump(array() == 0);
2.通过vld扩展查看此段代码的opcode
从上图的opcode中可以看出除了变量初始化,以及变量返回,并没有对变量比较做更多的展开。关键的内容其实在3行 IS_EQUAL 操作符具体的执行过程当中。于是在PHP文档中找到如下内容PHP: IS_EQUAL - Manual,只有一个关于该操作符的demo,并没有更多的内容。于是只剩下最后一招。
开源就是有这个好处,遇到问题逼不得已还可以看源码,一切也算是掌握之中(从这点上讲IOS的开发者很多情况就只能摸索了)。PHP的源码项目下载可以参见PHP: Git Access,本文参考PHP5.6的源码
在Zend引擎中,每个opcode根据操作参数的不同(上图中每一行的OP1、OP2)有25个opcode_handler,具体的配置可以参见zend_vm_excute.h 文件
static const void *zend_vm_get_opcode_handler(zend_uchar opcode, const zend_op* op) { static const int zend_vm_decode[] = { _UNUSED_CODE, /* 0 */ _CONST_CODE, /* 1 = IS_CONST */ _TMP_CODE, /* 2 = IS_TMP_VAR */ _UNUSED_CODE, /* 3 */ _VAR_CODE, /* 4 = IS_VAR */ _UNUSED_CODE, /* 5 */ _UNUSED_CODE, /* 6 */ _UNUSED_CODE, /* 7 */ _UNUSED_CODE, /* 8 = IS_UNUSED */ _UNUSED_CODE, /* 9 */ _UNUSED_CODE, /* 10 */ _UNUSED_CODE, /* 11 */ _UNUSED_CODE, /* 12 */ _UNUSED_CODE, /* 13 */ _UNUSED_CODE, /* 14 */ _UNUSED_CODE, /* 15 */ _CV_CODE /* 16 = IS_CV */ }; return zend_opcode_handlers[opcode * 25 + zend_vm_decode[op->op1_type] * 5 + zend_vm_decode[op->op2_type]]; }
该方法是在Zend引擎中根据opcode 与参数获取opcode handler的方法,可以看到是从zend_opcode_handlers数组中获取的。该数组在 zend_init_opcodes_handlers 方法中初始化,由于数组定义太长(4000行+)就不一一列出了,截取与IS_EQUAL 相关部分:
具体定位的过程,就将opcode的值带入公式
opcode * 25 + zend_vm_decode[op->op1_type] * 5 + zend_vm_decode[op->op2_type]
当中,opcode的值可以在PHP文档中查到,IS_EQUAL的opcode值为17,当然也可以看到handlers的命名还是很规范的,根据opcode的名字也可以搜索到。根据0x02中的opcode可以看出,两个参数的类型分别为IS_TMP_VAR,IS_CONST。定位到函数ZEND_IS_EQUAL_SPEC_TMPVAR_CONST_HANDLER中
static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_TMPVAR_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zend_free_op free_op1; zval *op1, *op2, *result; op1 = _get_zval_ptr_var(opline->op1.var, execute_data, &free_op1); op2 = EX_CONSTANT(opline->op2); do { int result; if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) { if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { result = (Z_LVAL_P(op1) == Z_LVAL_P(op2)); } else if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) { result = ((double)Z_LVAL_P(op1) == Z_DVAL_P(op2)); } else { break; } } else if (EXPECTED(Z_TYPE_P(op1) == IS_DOUBLE)) { if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) { result = (Z_DVAL_P(op1) == Z_DVAL_P(op2)); } else if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { result = (Z_DVAL_P(op1) == ((double)Z_LVAL_P(op2))); } else { break; } } else if (EXPECTED(Z_TYPE_P(op1) == IS_STRING)) { if (EXPECTED(Z_TYPE_P(op2) == IS_STRING)) { if (Z_STR_P(op1) == Z_STR_P(op2)) { result = 1; } else if (Z_STRVAL_P(op1)[0] > '9' || Z_STRVAL_P(op2)[0] > '9') { if (Z_STRLEN_P(op1) != Z_STRLEN_P(op2)) { result = 0; } else { result = (memcmp(Z_STRVAL_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op1)) == 0); } } else { result = (zendi_smart_strcmp(op1, op2) == 0); } zval_ptr_dtor_nogc(free_op1); } else { break; } } else { break; } ZEND_VM_SMART_BRANCH(result, 0); ZVAL_BOOL(EX_VAR(opline->result.var), result); ZEND_VM_NEXT_OPCODE(); } while (0); SAVE_OPLINE(); if ((IS_TMP_VAR|IS_VAR) == IS_CV && UNEXPECTED(Z_TYPE_P(op1) == IS_UNDEF)) { op1 = GET_OP1_UNDEF_CV(op1, BP_VAR_R); } if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_P(op2) == IS_UNDEF)) { op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R); } result = EX_VAR(opline->result.var); compare_function(result, op1, op2); ZVAL_BOOL(result, Z_LVAL_P(result) == 0); zval_ptr_dtor_nogc(free_op1); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); }
而根据具体的变量类型,IS_ARRAY、IS_LONG走了一圈的if else 发现最后又进了compare_function,该函数的实现可以在zend_operator.c文件中找到。由于函数比较长,switch了各种情况,就不把函数贴上来了。不过在这个函数里最初的两个问题得到了我最满意的解释。
google是解决问题的利器,好的文档也非常管用。偶尔自己深究一下,乐在其中。