摘要:的詞法分析器由生成,語(yǔ)法分析器則是由生成。這里最終返回一個(gè)的標(biāo)記。關(guān)于詞法分析器與語(yǔ)法分析器,這里講的并不多,希望后面有機(jī)會(huì)的話能夠再深入探討。
先直接來(lái)看一段展示:
# Psy Shell v0.3.3 (PHP 5.5.30 — cli) by Justin Hileman >>> ceil(-0.5) => -0.0 >>> max(-0.0, 0) => 0.0 >>> max(ceil(-0.5), 0) => -0.0
上面的演示中,ceil 函數(shù)返回的是 -0.0,max 在將 ceil 函數(shù)調(diào)用的結(jié)果作為參數(shù)傳入的時(shí)候,返回的也是一個(gè) -0.0。
如果給 ceil 的結(jié)果賦值給變量,還是能得到 -0.0 的結(jié)果:
>>> $a = ceil(-0.5) => -0.0 >>> max($a, 0) => -0.0
下面就來(lái)一一分析是哪些原因?qū)е铝诉@些結(jié)果的產(chǎn)生。
ceil 會(huì)返回 -0.0首先我們來(lái)看一下為什么 ceil 函數(shù)會(huì)返回 -0.0。
ceil 函數(shù)的實(shí)現(xiàn)在 $PHP-SRC/ext/stardands/math.c ($PHP-SRC 指的是 PHP 解釋器源碼根目錄)中,為了展示清楚我去掉了一些細(xì)節(jié):
PHP_FUNCTION(ceil) { ... if (Z_TYPE_PP(value) == IS_DOUBLE) { RETURN_DOUBLE(ceil(Z_DVAL_PP(value))); } else if (Z_TYPE_PP(value) == IS_LONG) { convert_to_double_ex(value); RETURN_DOUBLE(Z_DVAL_PP(value)); } ... }
從這里可以看出來(lái) ceil 函數(shù)做了兩個(gè)事情:
如果參數(shù)類(lèi)型是 double,則直接調(diào)用 C 語(yǔ)言的 ceil 函數(shù)并返回執(zhí)行結(jié)果;
如果參數(shù)類(lèi)型是 long,則轉(zhuǎn)換成 double 然后直接返回。
所以 ceil 返回 -0.0 這個(gè)本身的原因還在于 C。寫(xiě)個(gè)函數(shù)測(cè)試一下:
#include#include int main(int argc, char const *argv[]) { printf("%f ", ceil(-0.5)); return 0; }
以上代碼在我機(jī)器上的執(zhí)行結(jié)果是 -0.000000。至于為什么會(huì)是這個(gè)結(jié)果,這是 C 語(yǔ)言的問(wèn)題,這里也不細(xì)說(shuō),有興趣的可以看這里:http://www.wikiwand.com/zh/-0。
不能直接傳入 -0.0接下來(lái)討論一下為什么執(zhí)行 max(-0.0, 0) 卻得不到相同的結(jié)果。
用 vld 擴(kuò)展查看了一下只有以上一行代碼的 php 文件看一下結(jié)果:
line #* E I O op fetch ext return operands -------------------------------------------------------------------------- 3 0 E > EXT_STMT 1 EXT_FCALL_BEGIN 2 SEND_VAL 0 3 SEND_VAL 0 4 DO_FCALL 2 "max" 5 EXT_FCALL_END 5 6 > RETURN 1
注意到需要為 2 的 SEND_VAL 操作,送進(jìn)去的值是 0。也就說(shuō)在詞法分析階段之后 -0.0 就被轉(zhuǎn)換成 0 了。如何轉(zhuǎn)換的呢?下面我們簡(jiǎn)單的分析一下的過(guò)程。
PHP 的詞法分析器由 re2c 生成,語(yǔ)法分析器則是由 Bison 生成。在 zend_language_scanner.l ($PHP-SRC/Zend 目錄下)中我們可以找到以下的語(yǔ)句:
LNUM [0-9]+ DNUM ([0-9]*"."[0-9]+)|([0-9]+"."[0-9]*) EXPONENT_DNUM (({LNUM}|{DNUM})[eE][+-]?{LNUM}) ... ...{DNUM}|{EXPONENT_DNUM} { zendlval->value.dval = zend_strtod(yytext, NULL); zendlval->type = IS_DOUBLE; return T_DNUMBER; }
LNUM 和 DNUM 后面都是簡(jiǎn)單的正則表達(dá)式。雖然在詞法掃描中 0.0 會(huì)被標(biāo)記成 DNUM,并且位于 zend_strtod.c 的 zend_strtod 函數(shù)中的也有對(duì)于 加減號(hào)的處理,但是 - 符號(hào)并不和 DNUM 匹配(那既然這樣為什么 zend_strtod 還要處理加減號(hào)呢?因?yàn)檫@個(gè)函數(shù)不只是在這里使用的)。這里最終返回一個(gè) T_DNUMBER 的標(biāo)記。
再看 zend_language_parser.y 中:
common_scalar: T_LNUMBER { $$ = $1; } | T_DNUMBER { $$ = $1; } ... ; static_scalar: /* compile-time evaluated scalars */ common_scalar { $$ = $1; } ... | "+" static_scalar { ZVAL_LONG(&$1.u.constant, 0); add_function(&$2.u.constant, &$1.u.constant, &$2.u.constant TSRMLS_CC); $$ = $2; } | "-" static_scalar { ZVAL_LONG(&$1.u.constant, 0); sub_function(&$2.u.constant, &$1.u.constant, &$2.u.constant TSRMLS_CC); $$ = $2; } ... ;
同樣我們?nèi)サ袅艘恍┘?xì)節(jié),簡(jiǎn)單描述一下上面的語(yǔ)法分析的處理流程:
T_DNUMBER 是一個(gè) common_scalar 語(yǔ)句;
common_scalar 是一個(gè) static_scalar 語(yǔ)句;
static_scalar 語(yǔ)句前面存在減號(hào)時(shí),將操作數(shù) 1 (op1)設(shè)定為 值為 0 的 ZVAL_LONG ,然后調(diào)用 sub_function 函數(shù)處理兩個(gè)操作數(shù)。
sub_function 函數(shù)的實(shí)現(xiàn)位于 zend_operators.c 中,所做的操作很簡(jiǎn)單,就是用 op1 的值減去 op2 的值,所以就不會(huì)存在傳入 -0.0 的情況。
直接調(diào)用或賦值給變量既然如此,為什么直接使用函數(shù)調(diào)用做參數(shù)或者賦值給變量的方式又可以傳入呢?閑來(lái)看一下 zend_language_parser.y 中對(duì)于函數(shù)參數(shù)的分析語(yǔ)句:
function_call_parameter_list: "(" ")" { Z_LVAL($$.u.constant) = 0; } | "(" non_empty_function_call_parameter_list ")" { $$ = $2; } | "(" yield_expr ")" { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$2, ZEND_SEND_VAL, Z_LVAL($$.u.constant) TSRMLS_CC); } ; non_empty_function_call_parameter_list: expr_without_variable { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$1, ZEND_SEND_VAL, Z_LVAL($$.u.constant) TSRMLS_CC); } | variable { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$1, ZEND_SEND_VAR, Z_LVAL($$.u.constant) TSRMLS_CC); } | "&" w_variable { Z_LVAL($$.u.constant) = 1; zend_do_pass_param(&$2, ZEND_SEND_REF, Z_LVAL($$.u.constant) TSRMLS_CC); } ... ;
為了直觀 non_empty_function_call_parameter_list 語(yǔ)句塊后面我隱去了三行。后面三行的處理邏輯實(shí)際上是遞歸調(diào)用,并不影響我們分析。
通過(guò) function_call_parameter_list 可以看出函數(shù)的參數(shù)基本情況包括三種:
沒(méi)有參數(shù)
有參數(shù)列表
有 yield 表達(dá)式
這里我們只需要關(guān)注有參數(shù)列表的情況,參數(shù)列表中的每個(gè)參數(shù)也分三種情況:
不包含變量的表達(dá)式
變量
引用變量
上文中我們提到的直接傳入 -0.0 時(shí)對(duì)應(yīng)的是第一種情況,傳入賦值后的 $a 對(duì)應(yīng)的是第二種情況。參數(shù)最終都會(huì)交給 zend_do_pass_param 函數(shù)(zend_compile.c)去處理。
那么傳入 ceil(-0.5) 作為參數(shù)呢?實(shí)際上也是對(duì)應(yīng)第二種情況,這個(gè)問(wèn)題多帶帶分析起來(lái)也比較復(fù)雜,省事兒一點(diǎn)我們直接用 vld 看一下執(zhí)行 max(ceil(-0.5), 0)過(guò)程:
line #* E I O op fetch ext return operands -------------------------------------------------------------------------- 5 0 E > EXT_STMT 1 EXT_FCALL_BEGIN 2 EXT_FCALL_BEGIN 3 SEND_VAL -0.5 4 DO_FCALL 1 $0 "ceil" 5 EXT_FCALL_END 6 SEND_VAR_NO_REF 6 $0 7 SEND_VAL 0 8 DO_FCALL 2 "max" 9 EXT_FCALL_END 6 10 > RETURN 1
序號(hào)為 4 的語(yǔ)句中,ceil 的執(zhí)行結(jié)果是賦值給一個(gè) $0 的變量,而在序號(hào)為 6 的執(zhí)行中,執(zhí)行的是 SEND_VAR_NO_REF 的語(yǔ)句,調(diào)用的 $0。SEND_VAR_NO_REF 的 Opcode 是在何時(shí)被指定的呢?也是在 zend_do_pass_param 函數(shù)中:
if (op == ZEND_SEND_VAR && zend_is_function_or_method_call(param)) { /* Method call */ op = ZEND_SEND_VAR_NO_REF; ... }
函數(shù)執(zhí)行過(guò)程中使用 zend_parse_parameters 函數(shù)(zend_API.c)來(lái)獲取參數(shù)。從參數(shù)的存儲(chǔ)到獲取中間還有很多處理過(guò)程,這里不再一一詳解。但是需要知道一件事:函數(shù)在使用變量作為參數(shù)的時(shí)候是直接從已經(jīng)存儲(chǔ)的變量列表中讀取的,沒(méi)有經(jīng)過(guò)過(guò)濾處理,所以變量 $a 或 ceil(-0.5) 才可以直接將 -0.0 傳遞給 max 函數(shù)使用。
最后的原因既然以上都知道了,那還剩一個(gè)問(wèn)題:為什么在 -0.0 和 0 中 max 函數(shù)會(huì)選擇前者?
其實(shí)這個(gè)問(wèn)題很簡(jiǎn)單,看一下 max 函數(shù)的實(shí)現(xiàn)($PHP-SRC/ext/standard/array.c)就知道真的就是在兩值相等時(shí)選擇了前者:
max = args[0]; for (i = 1; i < argc; i++) { is_smaller_or_equal_function(&result, *args[i], *max TSRMLS_CC); if (Z_LVAL(result) == 0) { max = args[i]; } }
同樣,min 函數(shù)也存在這個(gè)問(wèn)題,區(qū)別就是 min 函數(shù)是調(diào)用的 is_smaller_function 來(lái)比較兩個(gè)數(shù)值,兩個(gè)值相等的時(shí)候返回前者。
所以要解決這個(gè)問(wèn)題也很簡(jiǎn)單,只需要調(diào)換一下參數(shù)順序即可:
# Psy Shell v0.3.3 (PHP 5.5.30 — cli) by Justin Hileman >>> max(0, ceil(-0.5)) => 0后話
本文僅僅是管中窺豹,從一個(gè)小 “bug” 入口簡(jiǎn)單的梳理一下各個(gè)環(huán)節(jié)的處理過(guò)程,如果想要更深入的理解 PHP 的執(zhí)行過(guò)程,還需要大量的精力和知識(shí)儲(chǔ)備。
分析 PHP 源碼的執(zhí)行過(guò)程不僅是為了對(duì) PHP 有更深刻的理解,也能幫助我們了解一門(mén)語(yǔ)言從代碼到執(zhí)行結(jié)果中間的各個(gè)環(huán)節(jié)和實(shí)現(xiàn)。
關(guān)于詞法分析器與語(yǔ)法分析器,這里講的并不多,希望后面有機(jī)會(huì)的話能夠再深入探討。re2c 的規(guī)則比較簡(jiǎn)單,關(guān)于 Bison,則有很多相關(guān)的書(shū)籍。
文中有粗淺的疏解,也留下有問(wèn)題,如有錯(cuò)誤,歡迎指正。
Stay foolish,stay humble; Keep questioning,keep learning.
私博地址:http://0x1.im
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/21210.html
摘要:記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。如果錢(qián)是負(fù)數(shù)了,還得從已生成的小紅包中抽取回來(lái)將紅包放入隊(duì)列之中創(chuàng)建紅包失敗,請(qǐng)檢查參數(shù)生產(chǎn)和之間的隨機(jī)數(shù),但是概率不是平均的,從到方向概率逐漸加大。 公司前段時(shí)間根據(jù)業(yè)務(wù)方需求需要做一個(gè)搶紅包的活動(dòng),網(wǎng)上也搜索了很多資料。記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。 產(chǎn)品需求: 1.紅包支持配置開(kāi)始時(shí)間、結(jié)束時(shí)間、類(lèi)型(隨機(jī)金額或固定金...
摘要:記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。如果錢(qián)是負(fù)數(shù)了,還得從已生成的小紅包中抽取回來(lái)將紅包放入隊(duì)列之中創(chuàng)建紅包失敗,請(qǐng)檢查參數(shù)生產(chǎn)和之間的隨機(jī)數(shù),但是概率不是平均的,從到方向概率逐漸加大。 公司前段時(shí)間根據(jù)業(yè)務(wù)方需求需要做一個(gè)搶紅包的活動(dòng),網(wǎng)上也搜索了很多資料。記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。 產(chǎn)品需求: 1.紅包支持配置開(kāi)始時(shí)間、結(jié)束時(shí)間、類(lèi)型(隨機(jī)金額或固定金...
摘要:記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。如果錢(qián)是負(fù)數(shù)了,還得從已生成的小紅包中抽取回來(lái)將紅包放入隊(duì)列之中創(chuàng)建紅包失敗,請(qǐng)檢查參數(shù)生產(chǎn)和之間的隨機(jī)數(shù),但是概率不是平均的,從到方向概率逐漸加大。 公司前段時(shí)間根據(jù)業(yè)務(wù)方需求需要做一個(gè)搶紅包的活動(dòng),網(wǎng)上也搜索了很多資料。記錄下整體的設(shè)計(jì)思路以及運(yùn)營(yíng)過(guò)程中的各種問(wèn)題。 產(chǎn)品需求: 1.紅包支持配置開(kāi)始時(shí)間、結(jié)束時(shí)間、類(lèi)型(隨機(jī)金額或固定金...
摘要:等平臺(tái)平臺(tái)由于我開(kāi)發(fā)以為主,所以就用的環(huán)境配置來(lái)學(xué)習(xí)。啟動(dòng)進(jìn)程的用戶(hù)和用戶(hù)組,進(jìn)程運(yùn)行的用戶(hù)必須要設(shè)置。模式模式,表示啟動(dòng)進(jìn)程是動(dòng)態(tài)分配的,隨著請(qǐng)求量動(dòng)態(tài)變化的。 centos等linux平臺(tái) /usr/local/php/php /usr/local/php/etc/php.ini /usr/local/php/sbin/php-fpm /usr/local/php/etc/php-...
閱讀 1188·2021-11-22 13:54
閱讀 2438·2021-09-22 15:36
閱讀 2743·2019-08-30 15:54
閱讀 812·2019-08-30 15:53
閱讀 3175·2019-08-30 15:53
閱讀 519·2019-08-29 15:21
閱讀 2873·2019-08-28 18:28
閱讀 3020·2019-08-26 13:37