国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

max/min 函數(shù)(PHP)的一個(gè)小 BUG

李濤 / 1521人閱讀

摘要:的詞法分析器由生成,語(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.0max 在將 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;
}

LNUMDNUM 后面都是簡(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ò)濾處理,所以變量 $aceil(-0.5) 才可以直接將 -0.0 傳遞給 max 函數(shù)使用。

最后的原因

既然以上都知道了,那還剩一個(gè)問(wèn)題:為什么在 -0.00max 函數(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

相關(guān)文章

  • 高并發(fā)紅包整體設(shè)計(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ī)金額或固定金...

    shenhualong 評(píng)論0 收藏0
  • 高并發(fā)紅包整體設(shè)計(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ī)金額或固定金...

    cheukyin 評(píng)論0 收藏0
  • 高并發(fā)紅包整體設(shè)計(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ī)金額或固定金...

    Freeman 評(píng)論0 收藏0
  • php-fpm配置和優(yōu)化

    摘要:等平臺(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-...

    AZmake 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<