遇到的问题最近遇到一个PHP大整数的问题。问题代码是这样的$shopId=17978812896666957068;var_dump($shopId);上面代码输出将$shopId转成float类型,用科学计数法表示,输出结果如下:float(1.7978812896667E+19)但是在程序中,需要完整的数字作为参数来查找数据,所以你需要使用它是一个完整的数字。当时以为只是因为数据转换成了科学计数法,所以想到的解决办法是强制不使用科学计数法:$shopId=number_format(17978812896666957068);var_dump($shopId);这时候奇怪的事情发生了,输出是:17978812896666957824我当时没仔细看,对比了前十就没有继续往下看,以为问题解决了,结果发现实际根据ID找数据时,找不到数据。这时发现数据转换不对。这里使用number_format失败的原因后面会说到。当时想到把原始数据转成字符串,但是下面的方法还是不行。.'';var_dump($shopId);输出结果都是float(1.7978812896667E+19)最后只有下面的方案可行:$shopId='17978812896666957068';var_dump($shopId);//output//string(20)"17978812896666957068"众所周知,PHP是一种解释型语言,所以我大胆猜测PHP在编译时将数字的字面常量转化为float类型,并用科学计数法表示。但是光靠猜测并不能满足我的好奇心,想要看到真正的实现代码才愿意相信。于是我逐渐分析探索,直到找到背后的实现。一开始我是根据这个问题直接在网上搜索“PHP大整数解析过程”,但是没有找到答案,只好自己找了。一开始对PHP的执行过程不熟悉,只能一步步调试入手,然后是示例代码://test.php$var=17978812896666957068;var_dump($var);跟踪过程1.查看opcode通过vld查看PHP执行代码的opcode,可以看到赋值是一个ASSIGNopcode操作。接下来,我想查看执行ASSIGN的位置。2.用gdb调试2-1。使用列表查看可以在何处设置断点2-2。暂时没有思路,尝试下1186下断点结果是程序到了sapi/cli/php_cli.c文件的第1200行,按n继续执行下一步直到这里是程序输出结果:2-4,所以可以猜测是在do_cli函数中进行了ASSIGN操作,所以在do_cli函数上打断点:breakdo_cli。输入n,连续回车。sapi/cli/php_cli.c文件993行后,会来到程序输出结果:2-5,然后在php_execute_script函数上打断点:breakphp_execute_script一步步连续执行,发现在main/main.c文件第2537行到达程序输出结果:2-6。继续断点的步骤:breakzend_execute_scripts重复前面的步骤,发现程序输出结果的步骤到了zend/Zend.c文件的1476行:看到这个的时候,1475行有一个op_array,所以我猜测它是否在op_array中有值,所以我开始打印op_array的值:打印后我没有看到任何有用的信息,但实际上这里包含了很多信息,例如操作码处理程序:ZEND_ASSIGN_SPEC_CV_RETVAL_CV_CONST_RETVAL_UNUSED_HANDLER,但是当时没注意,想看看op_array是怎么赋值的,相关步骤做了什么。2-7。再次从2-5的断点开始,让程序一步步执行,看到op_array的赋值如下:运行zend_compile_file函数的结果赋值给op_array,所以breakzend_compile_file,被告知zend_compile_fileundefined,通过源码工具追踪到zend_compile_file指向compile_file,所以breakzend_compile发现是Zend/zend_language_scanner.l文件中的断点,一步步执行。当我看到pass_two(op_array)这一行的时候,我猜测这里可能有一个值,于是打印出来:原来还是和之前一样,只是这个时候,我看到了一个opcodes的值。打印出来看到opcode=38,网上找的38代表赋值2-8,可以知道在这一步之前我已经拿到了。因此ASSIGN的opcode一直向前看,从op_array的初始化开始,逐渐打印op_array->opcodes的值,一直为null:直到执行CG(zend_lineno)=last_lineno;得到opcode=38Value:因为这句话:CG(zend_lineno)=last_lineno;是一个宏,所以我不知道,我快要放弃了。..于是我先去了解了opcode的数据结构,在深入理解PHP内核这本书中找到了opcode处理函数搜索一章,让我有了继续下去的思路。引用里面的内容:PHP中有一个函数可以快速返回特定opcode对应的opcode处理函数指针:zend_vm_get_opcode_handler()函数:知道opcode处理函数的命名有如下规则:ZEND_[opcode]_SPEC_(variabletype1)_(variabletype2)_HANDLER根据前面调试打印出来的内容,在2-6处看到一个handler值:ZEND_ASSIGN_SPEC_CV_CONST_RETVAL_UNUSED_HANDLER,找出函数的定义如下:可以看到,当opcode正在运行,值是从EX_CONSTANT中获取的。根据定义展开这个宏,即opline->op2->execute_data->literals这里可以得到两个信息:1.参数的转换是在opcode执行之前完成的。2.赋值过程取值在op2->execute_data->literals中。如果猜测正确,此时op2->execute_data->literals保存的是格式转换后的值。您可以将其打印出来并进行验证。打印结果如下:验证猜测正确,但是没有看到真正做转换的地方,所以还是不死心,继续找PHP的Zend底层编译逻辑代码。参考开源的GitHub项目,PHP编译阶段如下图所示:最有可能的猜测是在zendparse和zend_compile_top_stmt这两个阶段完成了转换,因为这两个阶段所做的就是转换PHP代码到一个操作码数组。在网上搜索了PHP语法分析相关的文章,其中一篇讲到了解析整数的过程,于是找到了PHP真正对大整数进行转换的地方:
