原文: https://blog.ircmaxell.com/2012/07/the-anatomy-of-equals-opcode-analysis.html
昨天有人通过电子邮件问我一个有趣的问题,问题相当简单,答案,不多……所以,与其用电子邮件回复,我想我应该写一篇关于它的文章。简单地说,问题是:当使用==将浮点数与整数进行比较时,在哪里进行转换?
那么,让我们开始:
测试代码:
在回答问题之前,我们需要做的第一步是定义我们可以分析流程的最简单代码。因此,我们可以从以下代码开始:
1 | <?php 1 == 1.0; |
但这有点太简单了,我们想知道变量会发生什么,但是没有变量!!!!因此,让我们添加一些变量:
1 | <?php |
现在我们状态很好,所以,下一步就是搞清楚到底发生了什么。
最简单的方法是查看生成的操作码,我用了5.4.4版本的Vulcan逻辑分解器。
The Opcodes
这4行PHP生成这8行操作码:
1 | line # * op fetch ext return operands |
现在,为了我们的目的,我们对操作码第5行感兴趣,这是一个等价的调用。
但在深入研究之前,让我们先谈谈我们已经提供的输出。
第一点有趣的信息在“op”列中,这告诉我们将要执行的操作,然后,右边有一堆数字。
前缀(~和!这里)是变量,其他是原始数值。~和!是吗?他们是一个编译变量(普通的PHP变量),其中~表示一个临时变量(直接将值从一个操作码传递到另一个操作码时使用)。
所以,在操作码5之前,我们刚刚将这两个值赋给了这两个变量(值得注意的是!1-因此$J-是一个浮点数,这里不输出小数)。因此,当我们到达第5行时,我们已经得到了所有需要的信息,以确定“等于”是什么。
Opcode Handlers
在我们深入研究is_equal之前,我们首先需要讨论操作码处理程序。当PHP生成它的VM(是的,它是生成的)时,它可以使用每个操作码的多个“版本”。对于is_equal,如果操作数是常量(1和1.0),而不是变量(变量处理代码需要不同),则可以想象获取参数所需的代码是不同的。
因此,处理程序是在zend/zend_vm_execute.h中定义的。它们具有命名约定:ZEND_{$OPCODE}_SPEC_{$VAR1_TYPE}_{$VAR2_TYPE}_HANDLER 。因此,通过观察IS_EQUALS 的调用,我们可以知道它们都是编译变量。因此,我们寻找 ZEND_IS_EQUALS_SPEC_CV_CV_HANDLER 的定义。果然,在第34805行。代码如下:
1 | static int ZEND_FASTCALL ZEND_IS_EQUAL_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) |
让我们忽略所有代码, 只看fast_equal_function。这些论点是非常直截了当的,除了时髦的“get_zval_ptr_blah_blah”行。这些都是获取变量值(zval,记得吗?)从操作码数组。然后,我们只需要调用fast_equal_function(result,var1,var2)。
Fast Equal Function
继续,我们需要查看fast_equal_function的内部,看看发生了什么。值得注意的是,到目前为止我们还没有修改任何变量。所以!0($i)和!1($j)仍然分别是一个整数和一个浮点。那么让我们来看看相等函数吧。
1 | static zend_always_inline int fast_equal_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) |
看起来里面发生了很多事情,但实际上,这很简单。
值得注意的一点是,expected()只是一个宏包装器,出于我们的目的,我们可以将它忽略。
所以,在函数里面,我们有3个分支。
- 第一个是如果第一个变量是整数。
- 第二个是如果第一个变量是一个浮点。(double也是浮点型)
- 如果第一个变量是其他变量,则会出现第三个分支和最后一个分支。
在前两个分支中的每一个,都有另外两个分支检查第二个参数是整数还是浮点。
如果是这样,它将进行简单的数字比较,如果不是,则调用下面的通用比较函数compare_function。
因此这就是我们的答案:
$I==$J 根本没有ZVAL强制转换,它只执行简单的C变量数值转换(基于值,不需要额外的内存分配)。
所以两个源变量都保持不变,没有进行“转换”。
结论
如果你知道该去哪里看的话,阅读资料并不难。试一试。有关练习,请查看[compare_function()](http://lxr.php.net/xref/php_5_4/zend/zend_operators.c 1402),并了解如果一个参数是字符串“2abc”,另一个是整数2,会发生什么情况…