本文首发先知社区
最近一直在学习PHP源码,在对PHP各个变量的实现有了一个大概的了解之后,尝试着对PHP的一些特性进行分析。在PHP源码分析方面,我算是一个初学者,如果有错误,欢迎师傅们批评指正。
前言
PHP
中有很多黑魔法,最初入门CTF的时候,就经常遇到考察PHP
弱类型的题,比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php error_reporting(0); include_once('flag.php'); highlight_file('index.php');
$md51 = md5('QNKCDZO'); $a = $_GET['b']; $md52 = md5($a); if(isset($a)){ if ($a != 'QNKCDZO' && $md51 == $md52) { echo $flag; } else { echo "false!!!"; } }
|
解决方案就是寻找一个MD5
值是0e
开头的字符串,PHP
在使用==
进行比较的时候,会认为该字符串是科学计数法表示的数字,然后又因为QNKCDZO
的MD5值是0e830400451993494058024219903391
,两个字符串都被转换为数字0
,从而使表达式$md51 == $md52
成立,但是如果是===
运算符,表达式就不会成立了。
对于变量之间的比较,手册中写的也挺详细的。
接下来根据PHP
的源码来分析下,这两个运算符是如何实现的。
环境&工具
- Mac Mojave 10.14
- PHP 7.1 + vld扩展
- VSCode debug
- UnderStand
1. PHP的弱类型实现
我们都知道PHP
中的变量本身是弱类型的,使用者在使用时不需要对变量类型进行声明,但PHP
的底层是用C
语言实现的,而C
语言中的变量是强类型的,使用时需要对变量类型进行声明。接下来我们基于PHP7
的源码,来简单分析下PHP
中的变量实现。
在PHP
中,所有的变量都是由一个zval
结构体来存储的。
路径:Zend/zend_types.h:121-143
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| struct _zval_struct { zend_value value; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) } v; uint32_t type_info; } u1; union { uint32_t next; uint32_t cache_slot; uint32_t lineno; uint32_t num_args; uint32_t fe_pos; uint32_t fe_iter_idx; uint32_t access_flags; uint32_t property_guard; } u2; };
|
变量真正的数据存储在value
中,也就是结构体_zend_value
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| typedef union _zend_value { zend_long lval; double dval; zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
|
而变量的类型通过联合体v
中的type
来表示。
路径Zend/zend_types.h:303
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10
#define IS_CONSTANT 11 #define IS_CONSTANT_AST 12
#define _IS_BOOL 13 #define IS_CALLABLE 14 #define IS_ITERABLE 19 #define IS_VOID 18
#define IS_INDIRECT 15 #define IS_PTR 17 #define _IS_ERROR 20
|
在真正取值的时候,Zend
虚拟机会根据获取的type
类型来获取对应的值。
比如我们执行代码$a = 1;
,在PHP
内部,$a
用zval
结构体来表示,它的u1.v.type==IS_LONG
,这表示它是一个长整型,它的value.lval==1
,这表示它的值为1
。
如果代码是$b = '123';
,那么它的u1.v.type==IS_STRING
,这表示它是一个字符串,它的value == zend_string *str
,真正的字符串123
存储在PHP
中的zend_string
结构体中。
总之,在PHP
中,无论是什么类型的变量,都是在zval
结构体中存储的,Zend
虚拟机面对的,始终是zval
结构体。
基于这种结构,PHP
中的变量成功实现了弱类型。
接下来我们看一下PHP
弱类型比较的实现过程。
2. ‘==’ && ‘===’ 的源码实现
2.1 前置知识
首先我们先了解一下PHP
的执行过程。
- 进行词法分析,将
PHP
代码转换为有意义的标识Token
,使用词法分析器Re2c
实现,将Zend/zend_language_scanner.l
文件编译为Zend/zend_language_scanner.c
。
- 进行语法分析,将
Token
和符合文法规则的代码生成抽象语法树。语法分析器基于Bison
实现,将Zend/zend_language_parser.y
文件编译为Zend/zend_language_parser.c
。
- 生成的抽象语法树生成对应的
opcode
,然后被虚拟机执行。opcode
对应着相应的处理函数,当虚拟机调用opcode
时,会找到opcode
对应的处理函数,执行真正的处理过程。
2.2 分析过程
测试代码
1 2 3 4
| <?php $a = "123"; var_dump($a == 123); var_dump($a === 123);
|
我们借助vld
扩展来看一下代码执行的opcode
。
可以看到,我们拿到了两个比较符对应的opcode
,很容易理解。
1 2
| '==' : IS_EQUAL // 相等 '===': IS_IDENTICAL // 完全相等
|
然后我们根据拿到的这两个opcode
,查找词法分析的源码来验证下。
路径:Zend/zend_language_scanner.l:1468
1 2 3
| <ST_IN_SCRIPTING>"===" { RETURN_TOKEN(T_IS_IDENTICAL) }
|
路径:Zend/zend_language_scanner.l:1476
1 2 3
| <ST_IN_SCRIPTING>"==" { RETURN_TOKEN(T_IS_EQUAL) }
|
我们可以知道,在词法分析时,标识Token
为T_IS_EQUAL
和T_IS_IDENTICAL
,
接下来语法分析的源码Zend/zend_language_parser.y
中查找。
路径:Zend/zend_language_parser.y:931
1 2
| | expr T_IS_IDENTICAL expr { $$ = zend_ast_create_binary_op(ZEND_IS_IDENTICAL, $1, $3); }
|
路径:Zend/zend_language_parser.y:935
1 2
| | expr T_IS_EQUAL expr { $$ = zend_ast_create_binary_op(ZEND_IS_EQUAL, $1, $3); }
|
可以知道,在语法分析中,调用生成opcode
的函数为zend_ast_create_binary_op
,生成的opcode
分别是ZEND_IS_EQUAL
和ZEND_IS_IDENTICAL
。
接下来就是去寻找opcode
对应的处理函数了。
路径:Zend/zend_vm_execute.h
根据Token
可以搜索到很多函数的声明,根据函数名以及我们上面的vld
扩展的输出,我们可以猜测,命名规则为
ZEND_IS_EQUAL_SPEC_
开头,接下来是OP1
和OP2
,然后以HANDLE
结尾。
ZEND_IS_IDENTICAL
对应函数的的声明也类似。
2.2.1 ‘==’ 源码实现分析
根据vld
扩展的输出,我们找到对应的函数ZEND_IS_EQUAL_SPEC_CV_CONST_HANDLER
。
路径:Zend/zend_vm_execute.h:36530
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE
zval *op1, *op2, *result;
op1 = _get_zval_ptr_cv_undef(execute_data, opline->op1.var); 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(Z_STR_P(op1), Z_STR_P(op2)) == 0); }
} 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_CV == 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);
ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); }
|
可以看到,如果前面的条件都没能成立,就会进入compare_function
函数。
首先我们查看一下调用关系,可以知道compare_function
是PHP
中变量对比的一个核心函数,
为了方便阅读,我把其中用到的宏放到了下面。
1 2 3 4 5 6
| #define TYPE_PAIR(t1,t2) (((t1) << 4) | (t2))
#define Z_DVAL(zval) (zval).value.dval #define Z_DVAL_P(zval_p) Z_DVAL(*(zval_p))
#define ZVAL_LONG(z, l)
|
路径:Zend/zend_operators.c:1976
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| ZEND_API int ZEND_FASTCALL compare_function(zval *result, zval *op1, zval *op2) { int ret; int converted = 0; zval op1_copy, op2_copy; zval *op_free, tmp_free;
while (1) { switch (TYPE_PAIR(Z_TYPE_P(op1), Z_TYPE_P(op2))) { case TYPE_PAIR(IS_LONG, IS_LONG): ZVAL_LONG(result, Z_LVAL_P(op1)>Z_LVAL_P(op2)?1:(Z_LVAL_P(op1)<Z_LVAL_P(op2)?-1:0)); return SUCCESS;
case TYPE_PAIR(IS_DOUBLE, IS_LONG): Z_DVAL_P(result) = Z_DVAL_P(op1) - (double)Z_LVAL_P(op2); ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result))); return SUCCESS;
case TYPE_PAIR(IS_LONG, IS_DOUBLE): Z_DVAL_P(result) = (double)Z_LVAL_P(op1) - Z_DVAL_P(op2); ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result))); return SUCCESS;
case TYPE_PAIR(IS_DOUBLE, IS_DOUBLE): if (Z_DVAL_P(op1) == Z_DVAL_P(op2)) { ZVAL_LONG(result, 0); } else { Z_DVAL_P(result) = Z_DVAL_P(op1) - Z_DVAL_P(op2); ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result))); } return SUCCESS;
case TYPE_PAIR(IS_ARRAY, IS_ARRAY): ZVAL_LONG(result, zend_compare_arrays(op1, op2)); return SUCCESS;
case TYPE_PAIR(IS_NULL, IS_NULL): case TYPE_PAIR(IS_NULL, IS_FALSE): case TYPE_PAIR(IS_FALSE, IS_NULL): case TYPE_PAIR(IS_FALSE, IS_FALSE): case TYPE_PAIR(IS_TRUE, IS_TRUE): ZVAL_LONG(result, 0); return SUCCESS;
case TYPE_PAIR(IS_NULL, IS_TRUE): ZVAL_LONG(result, -1); return SUCCESS;
case TYPE_PAIR(IS_TRUE, IS_NULL): ZVAL_LONG(result, 1); return SUCCESS;
case TYPE_PAIR(IS_STRING, IS_STRING): if (Z_STR_P(op1) == Z_STR_P(op2)) { ZVAL_LONG(result, 0); return SUCCESS; } ZVAL_LONG(result, zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2))); return SUCCESS;
case TYPE_PAIR(IS_NULL, IS_STRING): ZVAL_LONG(result, Z_STRLEN_P(op2) == 0 ? 0 : -1); return SUCCESS;
case TYPE_PAIR(IS_STRING, IS_NULL): ZVAL_LONG(result, Z_STRLEN_P(op1) == 0 ? 0 : 1); return SUCCESS;
case TYPE_PAIR(IS_OBJECT, IS_NULL): ZVAL_LONG(result, 1); return SUCCESS;
case TYPE_PAIR(IS_NULL, IS_OBJECT): ZVAL_LONG(result, -1); return SUCCESS;
default: ......
|
在最后的default
部分,我们会用到PHP
对象存储的相关知识,先来看下了解下PHP
中对象的存储结构。
路径:Zend/zend_types.h:276
1 2 3 4 5 6 7 8
| struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry *ce; const zend_object_handlers *handlers; HashTable *properties; zval properties_table[1]; };
|
以下是对对象进行操作的函数结构体定义,根据命名就能明白各个函数的功能是什么。
路径:Zend/zend_object_handlers.h:124
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| struct _zend_object_handlers { int offset; zend_object_free_obj_t free_obj; zend_object_dtor_obj_t dtor_obj; zend_object_clone_obj_t clone_obj; zend_object_read_property_t read_property; zend_object_write_property_t write_property; zend_object_read_dimension_t read_dimension; zend_object_write_dimension_t write_dimension; zend_object_get_property_ptr_ptr_t get_property_ptr_ptr; zend_object_get_t get; zend_object_set_t set; zend_object_has_property_t has_property; zend_object_unset_property_t unset_property; zend_object_has_dimension_t has_dimension; zend_object_unset_dimension_t unset_dimension; zend_object_get_properties_t get_properties; zend_object_get_method_t get_method; zend_object_call_method_t call_method; zend_object_get_constructor_t get_constructor; zend_object_get_class_name_t get_class_name; zend_object_compare_t compare_objects; zend_object_cast_t cast_object; zend_object_count_elements_t count_elements; zend_object_get_debug_info_t get_debug_info; zend_object_get_closure_t get_closure; zend_object_get_gc_t get_gc; zend_object_do_operation_t do_operation; zend_object_compare_zvals_t compare; };
|
大致了解了下对象的存储结构,我们接着往下看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| default: if (Z_ISREF_P(op1)) { op1 = Z_REFVAL_P(op1); continue; } else if (Z_ISREF_P(op2)) { op2 = Z_REFVAL_P(op2); continue; }
if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) { ret = Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2); if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) { convert_compare_result_to_long(result); } return ret; } else if (Z_TYPE_P(op2) == IS_OBJECT && Z_OBJ_HANDLER_P(op2, compare)) { ret = Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2); if (UNEXPECTED(Z_TYPE_P(result) != IS_LONG)) { convert_compare_result_to_long(result); } return ret; }
if (Z_TYPE_P(op1) == IS_OBJECT && Z_TYPE_P(op2) == IS_OBJECT) { if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) { ZVAL_LONG(result, 0); return SUCCESS; } if (Z_OBJ_HANDLER_P(op1, compare_objects) == Z_OBJ_HANDLER_P(op2, compare_objects)) { ZVAL_LONG(result, Z_OBJ_HANDLER_P(op1, compare_objects)(op1, op2)); return SUCCESS; } } if (Z_TYPE_P(op1) == IS_OBJECT) { if (Z_OBJ_HT_P(op1)->get) { zval rv; op_free = Z_OBJ_HT_P(op1)->get(op1, &rv); ret = compare_function(result, op_free, op2); zend_free_obj_get_result(op_free); return ret; } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) { ZVAL_UNDEF(&tmp_free); if (Z_OBJ_HT_P(op1)->cast_object(op1, &tmp_free, ((Z_TYPE_P(op2) == IS_FALSE || Z_TYPE_P(op2) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op2))) == FAILURE) { ZVAL_LONG(result, 1); zend_free_obj_get_result(&tmp_free); return SUCCESS; } ret = compare_function(result, &tmp_free, op2); zend_free_obj_get_result(&tmp_free); return ret; } } if (Z_TYPE_P(op2) == IS_OBJECT) { if (Z_OBJ_HT_P(op2)->get) { zval rv; op_free = Z_OBJ_HT_P(op2)->get(op2, &rv); ret = compare_function(result, op1, op_free); zend_free_obj_get_result(op_free); return ret; } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) { ZVAL_UNDEF(&tmp_free); if (Z_OBJ_HT_P(op2)->cast_object(op2, &tmp_free, ((Z_TYPE_P(op1) == IS_FALSE || Z_TYPE_P(op1) == IS_TRUE) ? _IS_BOOL : Z_TYPE_P(op1))) == FAILURE) { ZVAL_LONG(result, -1); zend_free_obj_get_result(&tmp_free); return SUCCESS; } ret = compare_function(result, op1, &tmp_free); zend_free_obj_get_result(&tmp_free); return ret; } else if (Z_TYPE_P(op1) == IS_OBJECT) { ZVAL_LONG(result, 1); return SUCCESS; } } if (!converted) { if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0); return SUCCESS; } else if (Z_TYPE_P(op2) == IS_NULL || Z_TYPE_P(op2) == IS_FALSE) { ZVAL_LONG(result, zval_is_true(op1) ? 1 : 0); return SUCCESS; } else if (Z_TYPE_P(op1) == IS_TRUE) { ZVAL_LONG(result, zval_is_true(op2) ? 0 : 1); return SUCCESS; } else if (Z_TYPE_P(op2) == IS_TRUE) { ZVAL_LONG(result, zval_is_true(op1) ? 0 : -1); return SUCCESS; } else { zendi_convert_scalar_to_number(op1, op1_copy, result, 1); zendi_convert_scalar_to_number(op2, op2_copy, result, 1); converted = 1; } } else if (Z_TYPE_P(op1)==IS_ARRAY) { ZVAL_LONG(result, 1); return SUCCESS; } else if (Z_TYPE_P(op2)==IS_ARRAY) { ZVAL_LONG(result, -1); return SUCCESS; } else if (Z_TYPE_P(op1)==IS_OBJECT) { ZVAL_LONG(result, 1); return SUCCESS; } else if (Z_TYPE_P(op2)==IS_OBJECT) { ZVAL_LONG(result, -1); return SUCCESS; } else { ZVAL_LONG(result, 0); return FAILURE; } } } }
|
总体来看,在进行==
运算的时候,虽然我们在写的时候只有短短的一句话,但是在PHP
内核实现的时候,却是考虑到了各种可能的情况,还进行了类型转换,从而实现了一个松散的判断相等的运算符。
对于类型转换,重点就是宏zendi_convert_scalar_to_number
,跟下去意义不是很大,有需要的可以查询官方手册
整个==
运算符的功能实现大概就这么多,接下来我们来看一下===
运算符的实现。
2.2.2 ‘===’ 源码实现分析
根据我们前面的分析,寻找ZEND_IS_IDENTICAL_SPEC_CV_CONST_HANDLER
函数。
路径:Zend/zend_vm_execute.h:36494
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_IS_IDENTICAL_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE
zval *op1, *op2; int result;
SAVE_OPLINE(); op1 = _get_zval_ptr_cv_deref_BP_VAR_R(execute_data, opline->op1.var); op2 = EX_CONSTANT(opline->op2); result = fast_is_identical_function(op1, op2);
ZEND_VM_SMART_BRANCH(result, 1); ZVAL_BOOL(EX_VAR(opline->result.var), result); ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); }
|
很明显,函数在获取OP1
和OP2
之后,进入了fast_is_identical_function
函数,跟进一下。
路径:Zend/zend_operators.h:748
1 2 3 4 5 6 7 8 9
| static zend_always_inline int fast_is_identical_function(zval *op1, zval *op2) { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { return 0; } else if (Z_TYPE_P(op1) <= IS_TRUE) { return 1; } return zend_is_identical(op1, op2); }
|
如果以上两个条件都不成立,进入zend_is_identical
函数并返回它的返回值,继续跟进。
路径:Zend/zend_operators.c:2004
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| ZEND_API int ZEND_FASTCALL zend_is_identical(zval *op1, zval *op2) { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { return 0; } switch (Z_TYPE_P(op1)) { case IS_NULL: case IS_FALSE: case IS_TRUE: return 1; case IS_LONG: return (Z_LVAL_P(op1) == Z_LVAL_P(op2)); case IS_RESOURCE: return (Z_RES_P(op1) == Z_RES_P(op2)); case IS_DOUBLE: return (Z_DVAL_P(op1) == Z_DVAL_P(op2)); case IS_STRING: return (Z_STR_P(op1) == Z_STR_P(op2) || (Z_STRLEN_P(op1) == Z_STRLEN_P(op2) && memcmp(Z_STRVAL_P(op1), Z_STRVAL_P(op2), Z_STRLEN_P(op1)) == 0)); case IS_ARRAY: return (Z_ARRVAL_P(op1) == Z_ARRVAL_P(op2) || zend_hash_compare(Z_ARRVAL_P(op1), Z_ARRVAL_P(op2), (compare_func_t) hash_zval_identical_function, 1) == 0); case IS_OBJECT: return (Z_OBJ_P(op1) == Z_OBJ_P(op2) && Z_OBJ_HT_P(op1) == Z_OBJ_HT_P(op2)); default: return 0; } }
|
经过以上分析我们可以知道,result
为1
时,返回true
,result
为0
时,返回false
。
===
运算符在内部实现上要比==
要简单的多,只有满足类型相同,对应的值也相同的变量才能满足条件,而且不会进行类型转换。
当然,在对变量值进行比较的过程中,不同的变量也会有不同的规则,比如数组。
在手册中,我们知道
具有较少成员的数组较小,如果运算数 1 中的键不存在于运算数 2 中则数组无法比较,否则挨个值比较
在zend_is_identical
中我们跟进zend_hash_compare
,可以找到zend_hash_compare_impl
。
路径:Zend/zend_hash.c:2313
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| static zend_always_inline int zend_hash_compare_impl(HashTable *ht1, HashTable *ht2, compare_func_t compar, zend_bool ordered) { uint32_t idx1, idx2;
if (ht1->nNumOfElements != ht2->nNumOfElements) { return ht1->nNumOfElements > ht2->nNumOfElements ? 1 : -1; }
for (idx1 = 0, idx2 = 0; idx1 < ht1->nNumUsed; idx1++) { Bucket *p1 = ht1->arData + idx1, *p2; zval *pData1, *pData2; int result;
if (Z_TYPE(p1->val) == IS_UNDEF) continue; if (ordered) { while (1) { ZEND_ASSERT(idx2 != ht2->nNumUsed); p2 = ht2->arData + idx2; if (Z_TYPE(p2->val) != IS_UNDEF) break; idx2++; } if (p1->key == NULL && p2->key == NULL) { if (p1->h != p2->h) { return p1->h > p2->h ? 1 : -1; } } else if (p1->key != NULL && p2->key != NULL) { if (ZSTR_LEN(p1->key) != ZSTR_LEN(p2->key)) { return ZSTR_LEN(p1->key) > ZSTR_LEN(p2->key) ? 1 : -1; }
result = memcmp(ZSTR_VAL(p1->key), ZSTR_VAL(p2->key), ZSTR_LEN(p1->key)); if (result != 0) { return result; } } else { return p1->key != NULL ? 1 : -1; } pData2 = &p2->val; idx2++; } else { if (p1->key == NULL) { pData2 = zend_hash_index_find(ht2, p1->h); if (pData2 == NULL) { return 1; } } else { pData2 = zend_hash_find(ht2, p1->key); if (pData2 == NULL) { return 1; } } }
pData1 = &p1->val; if (Z_TYPE_P(pData1) == IS_INDIRECT) { pData1 = Z_INDIRECT_P(pData1); } if (Z_TYPE_P(pData2) == IS_INDIRECT) { pData2 = Z_INDIRECT_P(pData2); }
if (Z_TYPE_P(pData1) == IS_UNDEF) { if (Z_TYPE_P(pData2) != IS_UNDEF) { return -1; } } else if (Z_TYPE_P(pData2) == IS_UNDEF) { return 1; } else { result = compar(pData1, pData2); if (result != 0) { return result; } } }
return 0; }
|
以下是手册中,===
在面对不同变量的时候运算结果表。
参考