资讯专栏INFORMATION COLUMN

PHP源码分析之等于操作符(==)

sushi / 1458人阅读

摘要:中不同类型的变量进行弱类型比较时,有其语言本身比较特殊的规则。方法源码如下操作数类型举例结果将转换为,进行数字或词汇比较源码说明结果将字符串和资源转换成数字,按普通数学比较源码说明其他各类型比较规则见于源码

PHP 中不同类型的变量进行弱类型比较时,有其语言本身比较特殊的规则。本文就起比较规则,就其源码角度进行解释。

PHP-version:5.3

官方规则

参考: PHP.net

规则解释

通过词法分析/语法分析/利用vld查看opcode,我发现 PHP 源码中,比较操作符实现的核心方法是 compare_function,位于 Zend/zend_operators.c +1376

确定核心方法源码位置 词法分析


如上图示,通过 Zend/zend_language_scanner.l +1201 词法分析规则,得到 == 对应的 Token 为:T_IS_EQUAL

语法分析


Zend/zend_language_parser.y 搜索上一步得到的 Token 值,得到语法分析中调用生成 opcode 的方法为:zend_do_binary_op,同时看到 == 所对应的 opcodeZEND_IS_EQUAL

确定 zend 执行时的函数

opcode 对应的调用函数实现于 Zend/zend_vm_execute.h 文件中,在此文件中搜索 ZEND_IS_EQUAL 得到如下的函数列表:

方法均以 ZEND_IS_EQUAL_SPEC 开头,名字后缀受 == 两个操作数的 zval 类型影响,具体的类型可以通过 vld 查看,如:

猜测是左操作数的类型 + 右操作数的类型。
以上图为例,左操作数类型为 IS_CONST, 右操作数类型为 IS_CV,则对应的处理方法应该是:ZEND_IS_EQUAL_SPEC_CONST_CV_HANDLER
(注:这种确认 handler 的方法只是经验规则,还有待源码验证)

以上的推测正确与否,不影响我们接下来的判断,因为我们发现这些方法都会调用同一个核心方法 compare_function,比较的规则就在这个方法中。通过看此方法源码,比较规则一目了然。

compare_function 方法源码如下:

ZEND_API int compare_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
{
    int ret;
    int converted = 0;
    zval op1_copy, op2_copy;
    zval *op_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)compare_objects(op1, op2 TSRMLS_CC));
                    return SUCCESS;
                }
                /* break missing intentionally */

            default:
                if (Z_TYPE_P(op1) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op1)->get) {
                        op_free = Z_OBJ_HT_P(op1)->get(op1 TSRMLS_CC);
                        ret = compare_function(result, op_free, op2 TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    } else if (Z_TYPE_P(op2) != IS_OBJECT && Z_OBJ_HT_P(op1)->cast_object) {
                        ALLOC_INIT_ZVAL(op_free);
                        if (Z_OBJ_HT_P(op1)->cast_object(op1, op_free, Z_TYPE_P(op2) TSRMLS_CC) == FAILURE) {
                            ZVAL_LONG(result, 1);
                            zend_free_obj_get_result(op_free TSRMLS_CC);
                            return SUCCESS;
                        }
                        ret = compare_function(result, op_free, op2 TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    }
                }
                if (Z_TYPE_P(op2) == IS_OBJECT) {
                    if (Z_OBJ_HT_P(op2)->get) {
                        op_free = Z_OBJ_HT_P(op2)->get(op2 TSRMLS_CC);
                        ret = compare_function(result, op1, op_free TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        return ret;
                    } else if (Z_TYPE_P(op1) != IS_OBJECT && Z_OBJ_HT_P(op2)->cast_object) {
                        ALLOC_INIT_ZVAL(op_free);
                        if (Z_OBJ_HT_P(op2)->cast_object(op2, op_free, Z_TYPE_P(op1) TSRMLS_CC) == FAILURE) {
                            ZVAL_LONG(result, -1);
                            zend_free_obj_get_result(op_free TSRMLS_CC);
                            return SUCCESS;
                        }
                        ret = compare_function(result, op1, op_free TSRMLS_CC);
                        zend_free_obj_get_result(op_free TSRMLS_CC);
                        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) {
                        zendi_convert_to_boolean(op2, op2_copy, result);
                        ZVAL_LONG(result, Z_LVAL_P(op2) ? -1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) == IS_NULL) {
                        zendi_convert_to_boolean(op1, op1_copy, result);
                        ZVAL_LONG(result, Z_LVAL_P(op1) ? 1 : 0);
                        return SUCCESS;
                    } else if (Z_TYPE_P(op1) == IS_BOOL) {
                        zendi_convert_to_boolean(op2, op2_copy, result);
                        ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(op1) - Z_LVAL_P(op2)));
                        return SUCCESS;
                    } else if (Z_TYPE_P(op2) == IS_BOOL) {
                        zendi_convert_to_boolean(op1, op1_copy, result);
                        ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(op1) - Z_LVAL_P(op2)));
                        return SUCCESS;
                    } else {
                        zendi_convert_scalar_to_number(op1, op1_copy, result);
                        zendi_convert_scalar_to_number(op2, op2_copy, result);
                        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;
                }
        }
    }
}
操作数类型举例 null

结果:将 NULL 转换为 "",进行数字或词汇比较

源码说明:

array

结果:将字符串和资源转换成数字,按普通数学比较

源码说明:

其他各类型比较规则见于 compare_function 源码

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/30689.html

相关文章

  • PHP7源码分析PHP7到底有多快,基准测试与特性分析告诉你

    摘要:我们修改上面代码,再来看下返回值类型限制的情况运行结果这段代码我们额外声明了返回值的类型为型。对函数返回值的声明做了扩充,可以定义其返回值为,无论是否开启严格模式,只要函数中有以外的其他语句都会报错。 顺风车运营研发团队 王坤 发表至21CTO公众号(https://mp.weixin.qq.com/s/ph...) showImg(https://segmentfault.c...

    Towers 评论0 收藏0
  • 后端ing

    摘要:当活动线程核心线程非核心线程达到这个数值后,后续任务将会根据来进行拒绝策略处理。线程池工作原则当线程池中线程数量小于则创建线程,并处理请求。当线程池中的数量等于最大线程数时默默丢弃不能执行的新加任务,不报任何异常。 spring-cache使用记录 spring-cache的使用记录,坑点记录以及采用的解决方案 深入分析 java 线程池的实现原理 在这篇文章中,作者有条不紊的将 ja...

    roadtogeek 评论0 收藏0
  • [PHP源码阅读]array_push和array_unshift函数

    摘要:对于函数,实现时新建一个哈希表,将需要插入的数据先插入到中,然后再把原来的数组数据写入到中,这样实现在数组前面插入数据元素的功能。这次阅读源码过程中,同时也研究了中的哈希表数据结构及一些,也给自己补充了一些哈希表的知识。 在PHP中,在数组中添加元素也是一种很常用的操作,分别有在数组尾部和头部添加元素,看看PHP内部是如何实现数组插入的操作。 我在github有对PHP源码更详细的注解...

    HackerShell 评论0 收藏0
  • Codeigniter 4.0-dev 版源码学习笔记七—— View 视图

    摘要:行处理视图文件名后缀。结语从源码上看,使用了原始作为模版机制使得视图逻辑非常简单。无非也就是把视图进来,用输出缓冲把执行结果拿到即可。此文可以转载,但转载前需要发邮件到进行沟通,未沟通的均视作侵权。 前言 CI 的 View 没有像 Laravel 等一些流行框架一样设计的那么重,有自己的一套模版机制,CI 一直采用纯天然的 PHP 模板形式,纯天然的好处是不用再学习一套模板语言了,缺...

    LiangJ 评论0 收藏0
  • JavaScript专题解读 v8 排序源码

    摘要:插入排序是稳定的算法。所以准确的说,当数组长度大于的时候,采用了快速排序和插入排序的混合排序方法。在对数组进行了一次快速排序后,然后对两个子集分别进行了插入排序,最终修改数组为正确排序后的数组。 JavaScript 专题系列第二十篇,也是最后一篇,解读 v8 排序源码 前言 v8 是 Chrome 的 JavaScript 引擎,其中关于数组的排序完全采用了 JavaScript 实...

    princekin 评论0 收藏0

发表评论

0条评论

sushi

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<