剖析 PHP 相等, OPCODE 分析

剖析 PHP 相等, OPCODE 分析

昨天有人通过电子邮件问了我一个有趣的问题。这个问题相当简单。答案,不是那么多。。。所以,我没有在电子邮件中回复,而是写了一篇关于它的文章。问题简单地说:当用==比较浮点数与整数时,在哪里发生转换?

我开始解释这个问题...

测试代码

因此,在回答这个问题之前,我们需要做的第一步是,定义我们能够分析这个过程的最简单的代码。那么,我们可以从这段代码开始:
<?php 1 = 1.0
但这有点太简单了。我们想知道变量的变化,但是没有变量!!因此,我们来添加一些变量:
<?php 
$i = 1;
$j = 1.0;
echo $i == $j;

现在我们的代码例子很好。下一步是搞清楚到底是怎么回事。最简单的方法是查看PHP 编译生成的操作码(OPCODE)。我用了Vulcan Logic Disassembler 5.4.4来做这个。

编译的OPCODE
什么的4行代码被编译成8行OPCODE:

line     # *  op           fetch  ext  return  operands
--------------------------------------------------------
   3     0  >   EXT_STMT
         1      ASSIGN                         !0, 1
   4     2      EXT_STMT
         3      ASSIGN                         !1, 1
   5     4      EXT_STMT
         5      IS_EQUAL               ~2      !0, !1
         6      ECHO                           ~2
   6     7    > RETURN                         1


现在,出于我们的目的,我们对opcode 第5行,IS_EQUAL 调用感兴趣。但在深入讨论之前,我们先来讨论一下我们已经提供的输出。第一个有趣的信息是在“op”列中。这告诉我们将要执行的操作(我们将会查找一个)。然后,右边有一堆数字。那些是预先固定的变量(~ 和!),而其他的是原始数值。~ 和 !两者之间的区别,!是一个编译后的变量(一个普通的PHP变量,!0 就是 $i, !1 就是$j),~它表示一个临时变量(用于直接从一个操作码传递值到另一个操作码时)。

因此,在opcode 行5之前,我们已经将两个值赋值给了两个变量(值得注意的是!1 (是一个变量,它的值是1,并且是浮点值,只不过没有输出来小数部分))。当我们到达第5行时,我们得到了需要的所有信息来计算IS_EQUAL 函数。


操作码处理程序
在我们深入探讨IS_EQUAL之前,我们需要先讨论一下操作码处理程序。当PHP生成它的VM(PHP 虚拟机)时(是的,PHP生成它的虚拟机),它有多个可以使用的操作码的“版本”。对于IS_EQUAL,您可以想象,如果操作数是常量(1和1.0)而不是变量(变量处理代码需要不同),那么获取参数所需的代码是不同的。

因此,处理程序在zend/zendvmexecute.h中定义。他们有命名约定(指ZEND API 约定):ZEND_{$OPCODE}_SPEC_{$VAR1_TYPE}_{$VAR2_TYPE}_HANDLER。因此,通过观察IS_EQUALS调用的操作数,我们可以看出他们都是编译的变量。因此,我们要寻找的是ZEND_IS_EQUAL_SPEC_CV_CV_HANDLER定义(指$VAR1_TYPE 是CV,$VAR2_TYPE 是CV)。当然,这是在34805(zend api)行。这是代码:

static int ZEND_FASTCALL  ZEND_IS_EQUAL_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval *result = &EX_T(opline->result.var).tmp_var;
    SAVE_OPLINE();
    ZVAL_BOOL(result, fast_equal_function(result,
        _get_zval_ptr_cv_BP_VAR_R(EX_CVs(), opline->op1.var TSRMLS_CC),
        _get_zval_ptr_cv_BP_VAR_R(EX_CVs(), opline->op2.var TSRMLS_CC) TSRMLS_CC));

    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}

让我们忽略所有的代码,除了fast_equal_function。这些参数都是非常直接的,只有一个例外,那就是_get_zval_ptr_cv_BP_VAR_R(),它的操作是从opcode数组中获取变量(zval,记得吗?)然后,我们简化fast_equal_function(result,var1,var2).

Fast Equal Function
继续,我们需要看看faste_qual_function的内部,看看会发生什么。需要注意的是,我们还没有对任何变量进行修改。如此!0($ i)和!1($j)仍然是一个整数和一个浮点数。我们来看看faste_qual_function的定义。

static zend_always_inline int fast_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC)
{
    if (EXPECTED(Z_TYPE_P(op1) == IS_LONG)) {
        if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
            return Z_LVAL_P(op1) == Z_LVAL_P(op2);
        } else if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) {
            return ((double)Z_LVAL_P(op1)) == Z_DVAL_P(op2);
        }
    } else if (EXPECTED(Z_TYPE_P(op1) == IS_DOUBLE)) {
        if (EXPECTED(Z_TYPE_P(op2) == IS_DOUBLE)) {
            return Z_DVAL_P(op1) == Z_DVAL_P(op2);
        } else if (EXPECTED(Z_TYPE_P(op2) == IS_LONG)) {
            return Z_DVAL_P(op1) == ((double)Z_LVAL_P(op2));
        }
    }
    compare_function(result, op1, op2 TSRMLS_CC);
    return Z_LVAL_P(result) == 0;
}

看起来好像有很多事情在发生。但实际上,这很简单。值得注意的一件事是,EXPECTED()只是一个我们不需要担心的宏包装函数,因为我们的目的,我们可以把它当作不存在的东西来对待。所以,在函数的根上,有3个分支。第一个分支是如果第一个变量是一个整数。第二个分支是第一个变量是浮点数。如果第一个变量是其他的类型,那么第三个也是最后一个分支。

在前两个分支的每个分支中,都有另外两个分支检查第二个参数是一个整数还是浮点数。如果是这样,它会进行一个简单的数值比较。如果没有,则返回到下面的通用比较函数。
这是我们的答案。$i==$j不做任何zval 类型转换。它只执行一个简单的C变量数值转换(它是基于值的,没有持久的副作用,并且不需要额外的内存分配)。因此,两个源变量都保持不变。没有“转换”。

结论
如果你知道该从哪里看,阅读源代码并不难。试一试。一个练习,看一看(compare_function()](http://lxr.php.net/xref/PHP_5_4/Zend/zend_operators.c # 1402),想想将会发生什么如果一个参数是一个字符串“2 abc”,另一个是一个整数2…
你有问题想让我试着回答吗?PHP内部是如何工作的?讲讲OO设计吗?与PHP相关的东西吗?给我发一封电子邮件ircmaxell@php.net,看看我能不能回答!

本文摘自 Anthony Ferrara 博客 https://blog.ircmaxell.com/2012/07/the-anatomy-of-equals-opcode-analysis.html
注意文章写得比较早,所以有些链接或代码可能不是最新的,请不要计较。
其他相关OPCODE 的文章
http://www.laruence.com/2008/06/18/221.html
http://blog.csdn.net/bly1126/article/details/6231907(安装VLD 查询OPCODE)
https://www.cnblogs.com/Alight/p/5257560.html
http://www.nowamagic.net/librarys/veda/detail/1325

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值