首頁 > 後端開發 > php教程 > 王帥:深入PHP核心(一)-弱型變項原理探究

王帥:深入PHP核心(一)-弱型變項原理探究

WBOY
發布: 2016-08-08 09:21:11
原創
1043 人瀏覽過

PHP是一門簡單而強大的語言,提供了許多Web適用的語言特性,其中就包含了變數弱型,在弱型別機制下,你能夠給一個變數賦任意型別的值。 
PHP的執行是透過Zend Engine(以下簡稱ZE),ZE是使用C編寫,在底層實現了一套弱型機制。 ZE的記憶體管理使用寫入時拷貝、引用計數等最佳化策略,減少再變量賦值時候的記憶體拷貝。

下面不光帶你探索PHP弱類型的原理,也會在寫PHP擴展角度,介紹如何操作PHP的變數。

1. PHP的變數型別

PHP的變數型別有8種:

  • 標準型別:布林boolen,整數型integer,浮點float,字元string
  • 複雜型別:陣列array,特殊類型:資源resource  
  • PHP不會嚴格檢驗變數類型,變數可以不顯示的宣告其類型,而在運作期間直接賦值。也可以將變數自由的轉換類型。如下例,沒有實作宣告的情況下,$i可以賦任意型別的值。

[php] view plaincopy

  1.  php  $i = 1;     // float $i = array(1 , 2, 3);  // array $i = new Exception('test', 123); // object $i = fopen('/tmp/aaa.txt', object $i = fopen('/tmp/aaa. 如果你對弱類型原理理解不深刻,在變數比較時候,會出現「超出預期」的驚喜。
[php] view plaincopy

 PHP 
    $str1
  1.  = null;  $str2 $str1==$str2 ? '相等' : '不相等';  $str3 = '' echo $str3== $str4 ? '相等' : '不相等';   = '0';   echo $str5==$str6 ? '相等'🎠 以上三個結果全部是相等,因為變數比較的時候,PHP內部做了變數轉換。如果希望值和類型同時判斷,請使用三個=(如,$a===0)來判斷。也許你會覺得司空見慣,也許你會覺得很神奇,那請跟我一起深入PHP內核,探索PHP變數原理。 2. 變量的存儲及標準類型介紹PHP的所有變量,都是以結構體zval來實現,在Zend/zend.h中我們能看到zval的定義:[php] view plaincopy
    1. typedef union _zvalue_value {     long lval;             double dval;               /* double v         char *val;         int len;             be set for strings */     } str;                     /* string (always has length) */     HashTable *ht;             /* an array */     zend_object_value obj;     /* stores an object store handle, and handlers */  } zvalue_value;   refcount__gc
    表示引用計數
    1valuetype
    is_ref__gc 表示是否為引用 0
    儲存變數的值  
     

    其中refcount__gc和is_ref__gc表示變數是否為一個引用。 type欄位標識變數的類型,type的值可以是:IS_NULL,IS_BOOL,IS_LONG,IS_FLOAT,IS_STRING,IS_ARRAY,IS_OBJECT,IS_RESOURCE。 PHP根據type的類型,來選擇如何儲存到zvalue_value。 
    zvalue_value能夠實現變數弱型態的核心,定義如下:

    [php] view plaincopy

    1. typedef union _zvalue_value {     long lval;             double dval;               /* doub le value                char *val;         int len;          will always be set for strings */     } str;                     /* string (always has length) */     HashTable *ht;             /* an array */     zend_object_value obj;     /* stores an object store handle, and handlers */  } zvalue_value;   布林型,zval.type
    2. 布爾型,zval.type如果是字串,zval.type=IS_STRING,會讀取zval.value.str,這是一個結構體,儲存了字串指標和長度。
    C語言中,用" plaincopy

    typedefstruct_zend_rsrc_list_entry {     void *ptr;     int 

    其中,ptr是一個指向資源的最終實現的指針,例如一個文件句柄,或者一個資料庫連接結構。 type是一個類型標記,用來區分不同的資源類型。 refcount用於資源的參考計數。

    核心中,資源類型是透過函數ZEND_FETCH_RESOURCE取得的。

    [php] view plaincopy

    1. ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type); 按照現在我們對PHP語言的了解,變數的類型依賴zval.type欄位指示,變數的內容依照zval.type儲存到zval.value。當PHP中需要變數的時候,只需要兩個步驟:把zval.value的值或指標改變,再改變zval.type的類型。不過對於PHP的一些高階變數Array/Object/Resource,變數轉換要進行更多操作。
    變數轉換原理分為3種:

    5.1 標準型別相互轉換

    比較簡單,依照上述的步驟轉換即可。

    5.2 標準型別與資源型別轉換

    資源型別可以理解為是int,較方便轉換標準型別。轉換後資源會被close或回收。

    [php] view plaincopy

     php 

    $var
    1.  = fopen()caaa/ // 資源 # 1 $var = (int) $var; var_dump($var);  // 輸出1 ?>  5.3 305.3 類型標準與複合型態轉換型/電腦型態轉換型/電腦型標準差35.3 305.300002 型式轉換類型/電腦類型。傳回元素個數;轉換bool回傳Array中是否有元素;轉換成string回傳'Array',並拋出warning。 詳情取決於經驗,請閱讀PHP手冊: http://php.net/manual/en/language.types.type-juggling.php 5.4 複雜型別相互轉換array和object可以互轉。如果其它任何類型的值被轉換成對象,將會建立一個內建類別stdClass的實例。
    2. 在我們寫PHP擴充的時候,PHP核心提供了一組函數用於型別轉換:  


    void convert_to_long(zval* p void convert_to_long_base(zval* pzval, int base)

    void convert_to_null(zval* pzval)

    void convert_to_leanf(Fvalzpvalp(Fpvalzp

    void convert_to_object( zval* pzval)void convert_object_to_type(zval* pzval, convert_func_t converter)PHP核提供的一組蛋白質來更容易的存取valval, 內核存取zval容器的API宏存取變數Z_DVAL(zval)
    (zval).value.dval
    Z_STRVAL(zval)

    (zval).value.str.val
    ). lenZ_ARRVAL(zval)Z_TYPE(zval) LVAL_P(zval)( *zval).value.lvalZ_DVAL_P(zval)(*zval).value.dvalZ_STRLEN_P(zval_p)毫(*zval).value.obj.handlers(**zval).value.lval_pp)(**zval).value.lval_pp)Z_STRVAL_PP(zval_pp)
    (zval). value.ht
    .
    (*zval).value.str.len
    Z_OBJ_HT_P(zval_p)
    Z_LVAL_PP(zval_pp)
    (**zval ).value.dval
    (**zval).value.str.val len
    Z_ARRVAL_PP(zval_pp) (**zval). value.ht

    6. 變數的符號表與作用域

    PHP的變數符號表與zval值的映射,是透過HashTable(哈希表,又叫做散列表,下面簡稱HT),HashTable在ZE中廣泛使用,包括常數、變數、函數等語言特性都是HT來組織,在PHP的陣列類型也是透過HashTable來實現。 
    舉例:

    [php] view plaincopy

    1.  php $var = 'Hello World';符號表中,代表$var的類型和值的zval結構儲存在哈希表中。核心透過變數符號表與zval位址的哈希映射,來實現PHP變數的存取。 為什麼要提作用域呢?因為函數內部變數保護。依照作用域PHP的變數分為全域變數和局部變量,每個作用域PHP都會維護一個符號表的HashTable。當在PHP中建立一個函數或類別的時候,ZE會建立一個新的符號表,表示函數或類別中的變數是局部變量,這樣就實現了局部變數的保護--外部無法存取函數內部的變數。當建立一個PHP變數的時候,ZE會分配一個zval,並設定對應type和初始值,把這個變數加入目前作用域的符號表,這樣使用者才能使用這個變數。 
    2. 核心中使用ZEND_SET_SYMBOL來設定變數:

    [php] view plaincopy


    ZEND_SET_SYMBOL( EG(active_symbol_table), 

    "foo"
    1. , foo); [php] view plaincopy
    2. Zend/zend_globals.h  

     struct _zend_   HashTable symbol_table;//全域變項的符號表        HashTable *active_symbol_table;//局部變項的符號表        ///略為@);        //略為略);

      

    1. 在寫PHP擴充時候,可以透過EG宏來存取PHP的變數符號表。 EG(symbol_table)存取全域作用域的變數符號表,EG(active_symbol_table)存取目前作用域的變數符號表,局部變數儲存的是指針,在對HashTable進行操作的時候傳遞給對應函數。 為了更好的理解變數的雜湊表與作用域,舉個簡單的例子:
    2. [php] view plaincopy
    3.  php 
    $temp

     = 

    'global'

    ;

    $temp

     = 
    1. 'active'; } test (); var_dump($temp); ?>   建立函數外的變數$temp,就會將這個它加入全域符號表,同時在全域符號表的HashTable中建立函數外的變數$temp,會把這個它加入全域符號表,同時在全域符號表的HashTable中類型的zval,值為'global'。建立函數test內部變數$temp,會把它加入屬於函數test的符號表,分配字元型zval,值為’active' 。 7. PHP擴充中變數操作建立PHP變數我們可以在擴充中呼叫函數MAKE_STD_ZVAL(pzv)來建立一個PHP可呼叫的變量,MAKE_STD_ZVAL應用到的宏有:] view plaincopy
      1. #define     MAKE_STD_ZVAL(zv)                               ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST)   ZEND) (type * ) emalloc(sizeof(type))   #define     INIT_PZVAL(z)               0;   

      MAKE_STD_ZVAL(foo)展開後得到:

      [php] view plaincopy

      1. (foo) = (zval *) emalloc(sizeof(zval));   (foo)->refcount__gc =__1;
      可以看出, MAKE_STD_ZVAL做了三件事:分配記憶體、初始化zval結構中的refcount、is_ref。 

      核心中提供一些巨集來簡化我們的操作,可以只用一步便設定好zval的類型和值。

      API Macros for Accessing zval 宏 Z_TYPE_P(pzv) = IS_BOOL; Z_BVAL_P(pzv) = b ? 1 : 0;ZVAL_TRUE(pvz) ZVAL_BOOL(pzv, 0);ZVAL_LONG(pvz, l)(l 是值)Z_TYPE_P(pzv) = IS_LONGZ_LLet z, d )ZVAL_STRINGL(pvz, stret, len,d,nid; pzv) = len; if (dup) { ZVAL_STRINGL(pzv, str,strlen(str), dup);ZVAL_RESOURCE(pvz, res)
      實作方法
      ZVAL_BOOL(pvz)
      FALSE(pvz)
      Z_TYPE_P(pzv) = IS_DOUBLE;Z_LVAL_P(pzv) = d;
          {Z_STRVAL_P(pzv) =estrndup(str, len + 1);}  }else {    pvz, str, len)
      ZVAL_RESOURCE(pvz,res)


      ZVAL_STRINGL(pzv,str,len,dup)中的dup參數

      先闡述一下ZVAL_STRINGL(pzv,str,len,dup); str和len兩個參數很好理解,因為我們知道內核中保存了字串的位址和它的長度,後面的dup的意思其實很簡單,它指明了該字串是否需要被複製。值為 1 將先申請一塊新記憶體並賦值該字串,然後把新記憶體的位址複製給pzv,為 0 時則是直接把str的位址賦值給zval。

      ZVAL_STRINGL與ZVAL_STRING的區別

      如果你想在某一位置截取該字串或已經知道了這個字串的長度,那麼可以使用宏ZVAL_STRINGL(zval, string, length, duplicate) ,它顯式的指定字串長度,而不是使用strlen()。這個宏該字串長度作為參數。但它是二進位安全的,而且速度也比ZVAL_STRING快,因為少了個strlen。 
      ZVAL_RESOURCE約等於ZVAL_LONG

      在章節4中我們說過,PHP中的資源類型的值是一個整數,所以ZVAL_RESOURCE和ZVAL_LONG的工作差不多,只不過它會把zval的類型設為 IS_RESOURCE。

      8. 總結

      PHP的弱類型是透過ZE的zval容器轉換完成,透過哈希表來儲存變數名稱和zval數據,在運作效率方面有一定犧牲。另外因為變數類型的隱性轉換,在開發過程中對變數類型檢測力度不夠,可能會導致問題出現。 

      不過PHP的弱型別、陣列、記憶體託管、擴充等語言特性,非常適合Web開發場景,開發效率很高,能夠加快產品迭代週期。在海量服務中,通常瓶頸存在於資料存取層,而不是語言本身。在實際使用PHP不僅擔任邏輯層和展現層的任務,我們甚至用PHP開發的UDPServer/TCPServer作為資料和cache的中間層。

      以上就介紹了王帥:深入PHP核心(一)-弱型變項原理探究,包含了面向的內容,希望對PHP教學有興趣的朋友有幫助。

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板