遇到的问题最近遇到一个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);//输出//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,所以猜测,ASSIGN操作是在do_cli函数中进行的,所以在do_cli函数上打断点:breakdo_cli。输入n,连续回车,sapi/cli/php_cli.c文件993行后,会来到程序输出结果:2-5,然后在php_execute_script函数上打断点:breakphp_execute_script,继续执行一步一步,发现在/main.c文件的main2537行到达程序的输出:2-6。继续断点步骤:breakzend_execute_scripts,重复前面的步骤,发现程序的输出到了zend/Zend.c文件的第1476行步骤:看到这个的时候,第1475行有个op_array,所以猜测op_array里面是不是有值,于是开始打印op_array的值:打印出来后没看到有用的信息,但其实这里有很多信息,比如opcodehandler:ZEND_ASSIGN_SPEC_CV_RETVAL_CV_CONST_RETVAL_UNUSED_HANDLER,但是当时没注意到,所以想看看op_array是怎么赋值的,相关步骤做了什么。2-7。再次从2-5的断点开始,让程序一步步执行,看到op_array的赋值如下:看到1470行将zend_compile_file函数运行的结果赋值给op_array,所以breakzend_compile_file,被告知zend_compile_file是undefined,通过源码工具追查到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的值,它在CG(zend_lineno)=last_lineno之前始终为null;被执行得到opcode=38的值:因为这句话: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这里可以得到两个信息:参数转换在opcode执行之前完成。op2->execute_data->literals,如果猜测正确,此时op2->execute_data->literals保存的是格式转换后的值,可以打印出来验证打印结果如下:猜测验证为没错,但是没有看到真正在转换的地方,我还是不死心,继续找PHP的Zend底层编译逻辑代码。参考开源的GitHub项目,PHP编译阶段如下图所示:最有可能的猜测是在zendparse和zend_compile_top_stmt这两个阶段完成了转换,因为这两个阶段所做的就是转换PHP代码到一个操作码数组。在网上搜索了PHP语法分析相关的文章,有一篇讲到了解析整数的过程,于是找到了PHP真正对大整数进行转换的地方:
