最近一直在学习PHP源码,在对PHP各个变量的实现有了一个大概的了解之后,尝试着对PHP的一些特性进行分析。在PHP源码分析方面,我算是一个初学者,如果有错误,欢迎师傅们批评指正。
PHP
中有很多黑魔法,最初入门CTF的时候,就经常遇到考察PHP
弱类型的题,比如
<?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
的源码来分析下,这两个运算符是如何实现的。
我们都知道PHP
中的变量本身是弱类型的,使用者在使用时不需要对变量类型进行声明,但PHP
的底层是用C
语言实现的,而C
语言中的变量是强类型的,使用时需要对变量类型进行声明。接下来我们基于PHP7
的源码,来简单分析下PHP
中的变量实现。
在PHP
中,所有的变量都是由一个zval
结构体来存储的。
路径:Zend/zend_types.h:121-143
struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* zval类型 */ zend_uchar type_flags, /* 对应变量类型特有的标记 */ zend_uchar const_flags, /* 常量类型的标记 */ zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; /* 与v是一个联合体,内存共享,修改该值等于修改结构体v的值。 */ } u1; union { uint32_t next; /* 用来解决hash冲突 */ uint32_t cache_slot; /* 运行时的缓存 */ uint32_t lineno; /* zend_ast_zval存行号 */ uint32_t num_args; /* EX(This)参数个数 */ uint32_t fe_pos; /* foreach的位置 */ uint32_t fe_iter_idx; /* foreach 游标的标记 */ uint32_t access_flags; /* 类的常量访问标识 */ // 常用的标识有 public、protected、 private uint32_t property_guard; /* 单一属性保护 */ // 防止类中魔术方法的循环调用 } u2; };
变量真正的数据存储在value
中,也就是结构体_zend_value
。
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; // zval类型 void *ptr; // 指针类型 zend_class_entry *ce; // class类型 zend_function *func; // function类型 struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
而变量的类型通过联合体v
中的type
来表示。
路径Zend/zend_types.h:303
/* 常规数据类型 */ #define IS_UNDEF 0 // 标记未使用类型 #define IS_NULL 1 // NULL #define IS_FALSE 2 // 布尔false #define IS_TRUE 3 // 布尔true #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 // 参考类型 /* constant expressions */ #define IS_CONSTANT 11 // 常量类型 #define IS_CONSTANT_AST 12 // 常量类型的AST数 /* 伪类型 */ #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
弱类型比较的实现过程。
首先我们先了解一下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
对应的处理函数,执行真正的处理过程。测试代码
<?php $a = "123"; var_dump($a == 123); var_dump($a === 123);
我们借助vld
扩展来看一下代码执行的opcode
。
可以看到,我们拿到了两个比较符对应的opcode
,很容易理解。
'==' : IS_EQUAL // 相等
'===': IS_IDENTICAL // 完全相等
然后我们根据拿到的这两个opcode
,查找词法分析的源码来验证下。
路径:Zend/zend_language_scanner.l:1468
<ST_IN_SCRIPTING>"===" {
RETURN_TOKEN(T_IS_IDENTICAL);
}
路径:Zend/zend_language_scanner.l:1476
<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
| expr T_IS_IDENTICAL expr
{ $$ = zend_ast_create_binary_op(ZEND_IS_IDENTICAL, $1, $3); }
路径:Zend/zend_language_parser.y:935
| 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
对应函数的的声明也类似。
根据vld
扩展的输出,我们找到对应的函数ZEND_IS_EQUAL_SPEC_CV_CONST_HANDLER
。
路径:Zend/zend_vm_execute.h:36530
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); // 获取OP1 op2 = EX_CONSTANT(opline->op2); // 获取OP2 do { int result; if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) { if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { // 如果OP1和OP2都是长整型,直接作比较并获得结果 result = (Z_LVAL_P(op1) == Z_LVAL_P(op2)); } else if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) { // 如果OP1是长整型,OP2是浮点型,对OP1进行强制类型转换为浮点型,然后再作比较。 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)) { // 如果OP1和OP2都是浮点型,直接作比较并获得结果 result = (Z_DVAL_P(op1) == Z_DVAL_P(op2)); } else if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) { // 如果OP1是浮点型,OP2是长整型,对OP2进行强制类型转换为浮点型,然后再作比较 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)) { // 如果OP1和OP2都是字符串 if (Z_STR_P(op1) == Z_STR_P(op2)) { // 取出OP1和OP2的zval.value.str结构体,判断是否相等 result = 1; } else if (Z_STRVAL_P(op1)[0] > '9' || Z_STRVAL_P(op2)[0] > '9') { // 如果OP1或者OP2的字符串开头不是数字 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);// 按字节来判断OP1和OP2的字符串结构体是否相等 } } else { result = (zendi_smart_strcmp(Z_STR_P(op1), Z_STR_P(op2)) == 0); // 使用zendi_smart_strcmp来判断OP1和OP2的字符串是否相等 } } 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(); // Zend虚拟机执行下一个opcode }
可以看到,如果前面的条件都没能成立,就会进入compare_function
函数。
首先我们查看一下调用关系,可以知道compare_function
是PHP
中变量对比的一个核心函数,
为了方便阅读,我把其中用到的宏放到了下面。
#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) // 将zval z的类型设置为长整型,值设置为l
路径:Zend/zend_operators.c:1976
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))) { // 获取OP1和OP2的type值,然后进行TYPE_PAIR运算 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): // OP1为浮点型,OP2为长整型 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): // OP1为长整型,OP2位浮点型 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): // OP1和OP2都为浮点型 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): // OP1和OP2都为数组 ZVAL_LONG(result, zend_compare_arrays(op1, op2)); return SUCCESS; case TYPE_PAIR(IS_NULL, IS_NULL): // OP1和OP2都为NULL case TYPE_PAIR(IS_NULL, IS_FALSE): // OP1为NULL,OP2为布尔型false case TYPE_PAIR(IS_FALSE, IS_NULL): // OP1为布尔型false,OP2为NULL case TYPE_PAIR(IS_FALSE, IS_FALSE): // OP1和OP2都为布尔型false case TYPE_PAIR(IS_TRUE, IS_TRUE): // OP1和OP2都为布尔型true ZVAL_LONG(result, 0); return SUCCESS; case TYPE_PAIR(IS_NULL, IS_TRUE): // OP1为NULL,OP2为布尔型true ZVAL_LONG(result, -1); return SUCCESS; case TYPE_PAIR(IS_TRUE, IS_NULL): // OP1为布尔型true,OP2为NULL ZVAL_LONG(result, 1); return SUCCESS; case TYPE_PAIR(IS_STRING, IS_STRING): // OP1和OP2都为字符串 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): // OP1是NULL,OP2是字符串 ZVAL_LONG(result, Z_STRLEN_P(op2) == 0 ? 0 : -1); return SUCCESS; case TYPE_PAIR(IS_STRING, IS_NULL): // OP1是字符串,OP2是NULL ZVAL_LONG(result, Z_STRLEN_P(op1) == 0 ? 0 : 1); return SUCCESS; case TYPE_PAIR(IS_OBJECT, IS_NULL): // OP1是对象,OP2是NULL ZVAL_LONG(result, 1); return SUCCESS; case TYPE_PAIR(IS_NULL, IS_OBJECT): // OP1是NULL,OP2是对象 ZVAL_LONG(result, -1); return SUCCESS; default: ......
在最后的default
部分,我们会用到PHP
对象存储的相关知识,先来看下了解下PHP
中对象的存储结构。
路径:Zend/zend_types.h:276
struct _zend_object { zend_refcounted_h gc; // 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
struct _zend_object_handlers { /* offset of real object header (usually zero) */ int offset; /* general object functions */ zend_object_free_obj_t free_obj; zend_object_dtor_obj_t dtor_obj; zend_object_clone_obj_t clone_obj; /* individual object functions */ 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; };
大致了解了下对象的存储结构,我们接着往下看。
default: if (Z_ISREF_P(op1)) { // 如果OP1是引用类型 op1 = Z_REFVAL_P(op1); // 获取OP1真正指向的zval continue; } else if (Z_ISREF_P(op2)) { // 如果OP1是引用类型 op2 = Z_REFVAL_P(op2); // 获取OP1真正指向的zval continue; } if (Z_TYPE_P(op1) == IS_OBJECT && Z_OBJ_HANDLER_P(op1, compare)) { // 如果OP1是对象,并且OP1的handlers.compare函数存在 ret = Z_OBJ_HANDLER_P(op1, compare)(result, op1, op2); // 使用OP1的handlers.compare函数进行对比操作 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)) { // 如果OP2是对象,并且OP2的handlers.compare函数存在 ret = Z_OBJ_HANDLER_P(op2, compare)(result, op1, op2); // 使用OP2的handlers.compare函数进行对比操作 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) { // 如果OP1和OP2都是对象 if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) { // 如果对象的handle相同,表示OP1和OP2是同一个对象 ZVAL_LONG(result, 0); return SUCCESS; } if (Z_OBJ_HANDLER_P(op1, compare_objects) == Z_OBJ_HANDLER_P(op2, compare_objects)) { // 如果OP1.handlers.compare_objects函数与OP2的相同,则调用该函数进行对比 ZVAL_LONG(result, Z_OBJ_HANDLER_P(op1, compare_objects)(op1, op2)); return SUCCESS; } } if (Z_TYPE_P(op1) == IS_OBJECT) { // 如果OP1是个对象 if (Z_OBJ_HT_P(op1)->get) { // OP1.handlers.get函数存在 zval rv; op_free = Z_OBJ_HT_P(op1)->get(op1, &rv); // 获取OP1的值 ret = compare_function(result, op_free, op2); // 递归调用compare_function zend_free_obj_get_result(op_free); return ret; } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) { // 如果OP2不是对象,并且OP1.handlers.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) { // 如果OP2是布尔型,则将OP1转换为布尔型,否则转换失败 ZVAL_LONG(result, 1); // OP1 > OP2 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) { // 如果OP2是个对象 if (Z_OBJ_HT_P(op2)->get) { // OP2.handlers.get函数存在 zval rv; op_free = Z_OBJ_HT_P(op2)->get(op2, &rv); // 获取OP2的值 ret = compare_function(result, op1, op_free); // 递归调用compare_function zend_free_obj_get_result(op_free); return ret; } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) { // 如果OP1不是对象,并且OP2.handlers.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) { // 如果OP1是布尔型,则将OP2转换为布尔型,否则转换失败 ZVAL_LONG(result, -1); // OP1 < OP2 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) { // converted为0 if (Z_TYPE_P(op1) == IS_NULL || Z_TYPE_P(op1) == IS_FALSE) { // 如果OP1是NULL或者布尔型false ZVAL_LONG(result, zval_is_true(op2) ? -1 : 0); // 如果OP2转换为布尔型是ture,则OP1 < OP2,否则,OP1 == OP2 return SUCCESS; } else if (Z_TYPE_P(op2) == IS_NULL || Z_TYPE_P(op2) == IS_FALSE) { // 如果OP2是NULL或者布尔型false ZVAL_LONG(result, zval_is_true(op1) ? 1 : 0); // 如果OP1转换为布尔型是ture,则OP1 > OP2,否则,OP1 == OP2 return SUCCESS; } else if (Z_TYPE_P(op1) == IS_TRUE) { // 如果OP1是布尔型true ZVAL_LONG(result, zval_is_true(op2) ? 0 : 1); // 如果OP2转换为布尔型是true,则OP1 == OP2,否则 OP1 > OP2 return SUCCESS; } else if (Z_TYPE_P(op2) == IS_TRUE) { // 如果OP2是布尔型true ZVAL_LONG(result, zval_is_true(op1) ? 0 : -1); // 如果OP1转换为布尔型是true,则OP1 == OP2,否则 OP1 < OP2 return SUCCESS; } else { zendi_convert_scalar_to_number(op1, op1_copy, result, 1); // 根据OP1的类型,转换为数字 zendi_convert_scalar_to_number(op2, op2_copy, result, 1); // 根据OP2的类型,转换为数字 converted = 1; // 标识已经经过了转换 } } else if (Z_TYPE_P(op1)==IS_ARRAY) { // 如果OP1的类型是数组 ZVAL_LONG(result, 1); // OP1 > OP2 return SUCCESS; } else if (Z_TYPE_P(op2)==IS_ARRAY) { // 如果OP2的类型是数组 ZVAL_LONG(result, -1); // OP1 < OP2 return SUCCESS; } else if (Z_TYPE_P(op1)==IS_OBJECT) { // 如果OP1的类型是对象 ZVAL_LONG(result, 1); // OP1 > OP2 return SUCCESS; } else if (Z_TYPE_P(op2)==IS_OBJECT) { // 如果OP2的类型是对象 ZVAL_LONG(result, -1); // OP1 < OP2 return SUCCESS; } else { ZVAL_LONG(result, 0); // OP1 == OP2 return FAILURE; } } } }
总体来看,在进行==
运算的时候,虽然我们在写的时候只有短短的一句话,但是在PHP
内核实现的时候,却是考虑到了各种可能的情况,还进行了类型转换,从而实现了一个松散的判断相等的运算符。
对于类型转换,重点就是宏zendi_convert_scalar_to_number
,跟下去意义不是很大,有需要的可以查询官方手册
整个==
运算符的功能实现大概就这么多,接下来我们来看一下===
运算符的实现。
根据我们前面的分析,寻找ZEND_IS_IDENTICAL_SPEC_CV_CONST_HANDLER
函数。
路径:Zend/zend_vm_execute.h:36494
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); // 获取OP1 op2 = EX_CONSTANT(opline->op2); // 获取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
static zend_always_inline int fast_is_identical_function(zval *op1, zval *op2) { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { // 如果OP1和OP2的类型不相同,返回0 return 0; } else if (Z_TYPE_P(op1) <= IS_TRUE) { // 可以看前面定义的宏来判断,如果OP1的类型是IS_UNDEF、IS_NULL、IS_FALSE、IS_TRUE,则返回1 return 1; } return zend_is_identical(op1, op2); }
如果以上两个条件都不成立,进入zend_is_identical
函数并返回它的返回值,继续跟进。
路径:Zend/zend_operators.c:2004
ZEND_API int ZEND_FASTCALL zend_is_identical(zval *op1, zval *op2) /* {{{ */ { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { // 如果OP1和OP2的类型不相同,返回0 return 0; } switch (Z_TYPE_P(op1)) { // 获取OP1的类型 case IS_NULL: case IS_FALSE: case IS_TRUE: // 如果是NULL和布尔型,则返回1 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: // 如果是数组,判断是否为同一个数组,或者调用zend_hash_compare进行判断 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: // 如果是对象,判断对象的值和对象指向的handler是否相同 return (Z_OBJ_P(op1) == Z_OBJ_P(op2) && Z_OBJ_HT_P(op1) == Z_OBJ_HT_P(op2)); default: return 0; // 不是上述已知类型,则返回0 } }
经过以上分析我们可以知道,result
为1
时,返回true
,result
为0
时,返回false
。
===
运算符在内部实现上要比==
要简单的多,只有满足类型相同,对应的值也相同的变量才能满足条件,而且不会进行类型转换。
当然,在对变量值进行比较的过程中,不同的变量也会有不同的规则,比如数组。
在手册中,我们知道
具有较少成员的数组较小,如果运算数 1 中的键不存在于运算数 2 中则数组无法比较,否则挨个值比较
在zend_is_identical
中我们跟进zend_hash_compare
,可以找到zend_hash_compare_impl
。
路径:Zend/zend_hash.c:2313
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));// 获取两个key对应的值来进行对比 if (result != 0) { // 当存在不相等的成员时,返回结果。 return result; } } else { /* Mixed key types: A string key is considered as larger */ 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) { // 如果变量是间接zval pData1 = Z_INDIRECT_P(pData1); // pData1获取它所指向的zval } if (Z_TYPE_P(pData2) == IS_INDIRECT) { // 如果变量是间接zval pData2 = Z_INDIRECT_P(pData2); // pData2获取它所指向的zval } if (Z_TYPE_P(pData1) == IS_UNDEF) { if (Z_TYPE_P(pData2) != IS_UNDEF) { // 如果pData1是未定义的变量,而pData2不是未定义的变量,则pData1所在的数组 < pData2所在的数组 return -1; } } else if (Z_TYPE_P(pData2) == IS_UNDEF) { // 如果pData1不是未定义的变量,而pData2是未定义的变量,则pData1所在的数组 > pData2所在的数组 return 1; } else { result = compar(pData1, pData2); // 如果两者都是不是未定义的变量,则进入compar进行比较 if (result != 0) { return result; } } } return 0; }
以下是手册中,===
在面对不同变量的时候运算结果表。