天天看點

【從源碼角度看php自增和自減】Happy coding.

自增和自減基礎

學過程式設計語言的同學應該都可以随口說出 ++a 和 a++ 的差別,具體的差別如下:

Example Name Effect
++

$

a
Pre-increment Increments abyone,thenreturns a.

$

a++
Post-increment Returns a,thenincrements a by one.

$

a
Pre-decrement Decrements abyone,thenreturns a.

$

a–
Post-decrement Returns a,thendecrements a by one.

++a 表示取 a 的位址,增加記憶體中 a 的值,然後把值放在寄存器中。

a++ 表示取 a 的位址,把 a 的值裝入寄存器中,然後增加記憶體中 a 的值。

php 中的遞增和遞減

  • 官方文檔:

    http://php.net/manual/zh/language.operators.increment.php

在php中,通常情況下的遞增和遞減沒什麼特别,下面我們看一些特殊的遞增和遞減。

$a = NULL;	$a++;   //int 1                                  
$a = NULL;	$a--;   //null                                          
$a = true;	$a++;   //true                              
$a = true;	$a--;   //true                              
$a = false;	$a++;   //false                                 
$a = false;	$a--;   //false                                     
           
從上面可以看出,php中遞增和遞減運算符不影響布爾值;遞減NULL值沒有效果,但是遞增NULL值的結果為1。
$a = 'C';	$a++;   //string D                                
$a = 'C';	$a--;   //string C                                
$a = 'C2';	$a++;   //string C3                               
$a = 'C2';	$a--;   //string C2                               
$a = '2C';	$a++;  //string D                               
$a = '2C';	$a--;  //string C                               
           
從上面可以看出,在php中,字元串支援遞增,但是不支援遞減。

再看一下下面的代碼:

$a = 'Z';	$a++;  //string AA                               
$a = 'C9';	$a++;  //string D0                               
           

‘A’執行遞增,結果為’B’;‘Z’執行遞增,結果為’AA‘,’C9’遞增,結果為’D0’。這和Perl相似。

php源碼分析:

$

a++與++

$

a

首先說明一下,此處的分析基于 php5.6 的源碼分析。

對于字首自增(++$a),包含的opcode為PRE_INC,其最終調用的是Zend/zend_vm_execute.h檔案中的ZEND_PRE_INC_SPEC_CV_HANDLER函數,源碼如下:

static int ZEND_FASTCALL  ZEND_PRE_INC_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval **var_ptr;

    SAVE_OPLINE();
    var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_RW(execute_data, opline->op1.var TSRMLS_CC);

    if (IS_CV == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
        zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
    }
    if (IS_CV == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
        if (RETURN_VALUE_USED(opline)) {
            PZVAL_LOCK(&EG(uninitialized_zval));
            EX_T(opline->result.var).var.ptr = &EG(uninitialized_zval);
        }

        CHECK_EXCEPTION();
        ZEND_VM_NEXT_OPCODE();
    }

    SEPARATE_ZVAL_IF_NOT_REF(var_ptr);

    if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
       && Z_OBJ_HANDLER_PP(var_ptr, get)
       && Z_OBJ_HANDLER_PP(var_ptr, set)) {
        /* proxy object */
        zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
        Z_ADDREF_P(val);
        fast_increment_function(val);
        Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
        zval_ptr_dtor(&val);
    } else {
        fast_increment_function(*var_ptr);
    }

    if (RETURN_VALUE_USED(opline)) {
        PZVAL_LOCK(*var_ptr);
        EX_T(opline->result.var).var.ptr = *var_ptr;
    }

    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
}
           

fast_increment_function函數實際調用的是increment_function函數:

ZEND_API int increment_function(zval op1) 
{
    switch (Z_TYPE_P(op1)) {
        case IS_LONG:
            if (Z_LVAL_P(op1) == LONG_MAX) {
                /* switch to double */
                double d = (double)Z_LVAL_P(op1);
                ZVAL_DOUBLE(op1, d+);
            } else {
            Z_LVAL_P(op1)++;
            }
            break;
        case IS_DOUBLE:
            Z_DVAL_P(op1) = Z_DVAL_P(op1) + ;
            break;
        case IS_NULL:
            ZVAL_LONG(op1, );
            break;
        case IS_STRING: {
                long lval;
                double dval;

            switch (is_numeric_string(Z_STRVAL_P(op1), Z_STRLEN_P(op1), &lval, &dval, )) {
                    case IS_LONG:
                        str_efree(Z_STRVAL_P(op1));
                        if (lval == LONG_MAX) {
                            /* switch to double */
                            double d = (double)lval;
                            ZVAL_DOUBLE(op1, d+);
                        } else {
                            ZVAL_LONG(op1, lval+);
                        }
                        break;
                    case IS_DOUBLE:
                        str_efree(Z_STRVAL_P(op1));
                        ZVAL_DOUBLE(op1, dval+);
                        break;
                    default:
                        /* Perl style string increment */
                        increment_string(op1);
                        break;
                }
            }
            break;
        case IS_OBJECT:
            if (Z_OBJ_HANDLER_P(op1, do_operation)) {
                zval *op2;
                int res;
                TSRMLS_FETCH();

                MAKE_STD_ZVAL(op2);
                ZVAL_LONG(op2, );
            res = Z_OBJ_HANDLER_P(op1, do_operation)(ZEND_ADD, op1, op1, op2 TSRMLS_CC);
                zval_ptr_dtor(&op2);

                return res;
            }
            return FAILURE;
        default:
            return FAILURE;
    }
    return SUCCESS;
}
           

首先調用_get_zval_ptr_ptr_cv_BP_VAR_RW函數擷取CV(Compiled variable)類型變量;

其次會調用fast_increment_function函數,再調用increment_function函數,實作變量的增加操作,

在increment_function函數中,會根據變量的類型來進行對應的操作,從上面可以看出,依次判斷的類型有IS_LONG/IS_DOUBLE/IS_NULL/IS_STRING/IS_OBJECT。

如果是IS_LONG類型,若變量達到long的最大值,則将其轉化為double類型後加1,否則直接加1;

如果是IS_DOUBLE類型,則直接加1;

如果是IS_NULL類型,則會調用宏ZVAL_LONG(op1, 1),直接傳回long類型的1;

如果是IS_STRING類型,則會先将其轉化為數字類型,然後再根據上面判斷數字類型的邏輯判斷;

如果是IS_OBJECT類型,并且其内部定義了運算符操作的實作,那就調用這個handler來處理,進行

簡言之,字首自增實際上操作的是變量本身,在表達式中使用的也是變量本身。

對于字尾自增($a++),包含的opcode為POST_INC,其最終調用的是Zend/zend_vm_execute.h檔案中的ZEND_POST_INC_SPEC_CV_HANDLER函數,源碼如下:

static int ZEND_FASTCALL  ZEND_POST_INC_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval **var_ptr, *retval;

    SAVE_OPLINE();
    var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_RW(execute_data, opline->op1.var TSRMLS_CC);
    if (IS_CV == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
        zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
    }
    if (IS_CV == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
        ZVAL_NULL(&EX_T(opline->result.var).tmp_var);

        CHECK_EXCEPTION();
        ZEND_VM_NEXT_OPCODE();
    }

    retval = &EX_T(opline->result.var).tmp_var;
    ZVAL_COPY_VALUE(retval, *var_ptr);
    zendi_zval_copy_ctor(*retval);

    SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
    if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT)
    && Z_OBJ_HANDLER_PP(var_ptr, get)
       && Z_OBJ_HANDLER_PP(var_ptr, set)) {
        /* proxy object */
        zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC);
        Z_ADDREF_P(val);
        fast_increment_function(val);
        Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC);
        zval_ptr_dtor(&val);
    } else {
        fast_increment_function(*var_ptr);
    }

    CHECK_EXCEPTION();
    ZEND_VM_NEXT_OPCODE();
} 
           

從上面可以看出,字尾自增與字首自增在底層實作的大部分是類似的,但是不同的點在于,字尾自增多了一個臨時變量,用于存儲原始的變量的值,但是它并沒有字首自增的RETURN_VALUE_USED操作。

簡言之,字尾自增使用的是存放在臨時變量中的值,即變量的原始值,而最終變量本身的值還是會增加。

一些奇怪的php自增和自減

$a = '2D9';	$a++;  //string E
$a = '2E0';	$a++;  //float 
$a = '010';	$a++;  //
$a = ;	$a++;  //
           

第一個很好了解。

對于第二個,并不是我們想的 ‘2E1’,而是3。注意輸出結果為 float 3,原來這裡

$

a=’2E0’,對

$

a執行遞增,由于字元串

$

a中包含’E’,是以會被當作float來取值。科學計數法 2E0 表示 2*10^0 值為2,對其加1則結果為3。

對于第三個,

$

a=’010’,對

$

a執行遞增,字元串

$

a會被當作integer來處理,即為10,對其加1則結果為11。

對于第四個,

$

a=010,對

$

a執行遞增,

$

a本來就是數字類型,由于是0開頭,表示8進制,即為8,對其加1則結果為9。

Happy coding.

轉載請注明出處: @CSU-Max http://blog.csdn.net/csu_max