王帥:深入PHP核心(一)——弱類型變數原理探究

來源:互聯網
上載者:User
PHP是一門簡單而強大的語言,提供了很多Web適用的語言特性,其中就包括了變數弱類型,在弱類型機制下,你能夠給一個變數賦任意類型的值。
PHP的執行是通過Zend Engine(下面簡稱ZE),ZE是使用C編寫,在底層實現了一套弱類型機制。ZE的記憶體管理使用寫時拷貝、引用計數等最佳化策略,減少再變數賦值時候的記憶體拷貝。

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

1. PHP的變數類型

PHP的變數類型有8種:

  • 標準類型:布爾boolen,整型integer,浮點float,字元string
  • 複雜類型:數組array,對象object
  • 特殊類型:資源resource

PHP不會嚴格檢驗變數類型,變數可以不顯示的聲明其類型,而在運行期間直接賦值。也可以將變數自由的轉換類型。如下例,沒有實現聲明的情況下,$i可以賦任意類型的值。

[php] view plaincopy

  1. $i = 1; //int $i = 'show me the money'; //string $i = 0.02; // float $i = array(1, 2, 3); // array $i = new Exception('test', 123); // object $i = fopen('/tmp/aaa.txt', 'a') // resource ?>

如果你對弱類型原理理解不深刻,在變數比較時候,會出現“超出預期”的驚喜。

[php] view plaincopy

  1. $str1 = null; $str2 = false; echo $str1==$str2 ? '相等' : '不相等'; $str3 = ''; $str4 = 0; echo $str3==$str4 ? '相等' : '不相等'; $str5 = 0; $str6 = '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; /* long value */ double dval; /* double value */ struct { char *val; int len; /* this 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;

屬性名稱 含義 預設值
refcount__gc 表示引用計數 1
is_ref__gc 表示是否為引用 0
value 儲存變數的值
type 變數具體的類型

其中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; /* long value */ double dval; /* double value */ struct { char *val; int len; /* this 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=IS_BOOL,會讀取zval.value.lval欄位,值為1/0。如果是字串,zval.type=IS_STRING,會讀取zval.value.str,這是一個結構體,儲存了字串指標和長度。

C語言中,用"\0"作為字串結束符。也就是說一個字串"Hello\0World"在C語言中,用printf來輸出的話,只能輸出hello,因為"\0"會認為字元已經結束。PHP中是通過結構體的_zval_value.str.len來控制字元串長度,相關函數不會遇到"\0"結束。所以PHP的字串是二進位安全的。

如果是NULL,只需要zval.type=IS_NULL,不需要讀取值。

通過對zval的封裝,PHP實現了弱類型,對於ZE來說,通過zval可以存取任何類型。

3. 進階類型Array和Object數組Array

數組是PHP語言中非常強大的一個資料結構,分為索引數組和關聯陣列,zval.type=IS_ARRAY。在關聯陣列中每個key可以儲存任意類型的資料。PHP的數組是用Hash Table實現的,數組的值存在zval.value.ht中。

後面會專門講到PHP雜湊表的實現。

物件類型的zval.type=IS_OBJECT,值存在zval.value.obj中。

4. 特殊類型——資源類型(Resource)介紹

資源類型是個很特殊的類型,zval.type=IS_RESOURCE,在PHP中有一些很難用常規類型描述的資料結構,比如檔案控制代碼,對於C語言來說是一個指標,不過PHP中沒有指標的概念,也不能用常規類型來約束,因此PHP通過資源類型概念,把C語言中類似檔案指標的變數,用zval結構來封裝。資源類型值是一個整數,ZE會根據這個值去資源的雜湊表中擷取。

資源類型的定義:

[php] view plaincopy

  1. typedefstruct_zend_rsrc_list_entry { void *ptr; int type; int refcount; }zend_rsrc_list_entry;

其中,ptr是一個指向資源的最終實現的指標,例如一個檔案控制代碼,或者一個資料庫連接結構。type是一個類型標記,用於區分不同的資源類型。refcount用於資源的引用計數。

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

[php] view plaincopy

  1. ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type);

5. 變數類型的轉換

按照現在我們對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

  1. $var = fopen('/tmp/aaa.txt', 'a'); // 資源 #1 $var = (int) $var; var_dump($var); // 輸出1 ?>

5.3 標準類型與複雜類型轉換

Array轉換整型int/浮點型float會返回元素個數;轉換bool返回Array中是否有元素;轉換成string返回'Array',並拋出warning。
詳細內容取決於經驗,請閱讀PHP手冊: http://php.net/manual/en/language.types.type-juggling.php

5.4 複雜類型相互轉換

array和object可以互轉。如果其它任何類型的值被轉換成對象,將會建立一個內建類stdClass的執行個體。

在我們寫PHP擴充的時候,PHP核心提供了一組函數用於類型轉換:

void convert_to_long(zval* pzval)
void convert_to_double(zval* pzval)
void convert_to_long_base(zval* pzval, int base)
void convert_to_null(zval* pzval)
void convert_to_boolean(zval* pzval)
void convert_to_array(zval* pzval)
void convert_to_object(zval* pzval)
void convert_object_to_type(zval* pzval, convert_func_t converter)

PHP核心提供的一組宏來方便的訪問zval,用於更細粒度的擷取zval的值:

核心訪問zval容器的API
訪問變數
Z_LVAL(zval) (zval).value.lval
Z_DVAL(zval) (zval).value.dval
Z_STRVAL(zval) (zval).value.str.val
Z_STRLEN(zval) (zval).value.str.len
Z_ARRVAL(zval) (zval). value.ht
Z_TYPE(zval) (zval).type
Z_LVAL_P(zval) (*zval).value.lval
Z_DVAL_P(zval) (*zval).value.dval
Z_STRVAL_P(zval_p) (*zval).value.str.val
Z_STRLEN_P(zval_p) (*zval).value.str.len
Z_ARRVAL_P(zval_p) (*zval). value.ht
Z_OBJ_HT_P(zval_p) (*zval).value.obj.handlers
Z_LVAL_PP(zval_pp) (**zval).value.lval
Z_DVAL_PP(zval_pp) (**zval).value.dval
Z_STRVAL_PP(zval_pp) (**zval).value.str.val
Z_STRLEN_PP(zval_pp) (**zval).value.str.len
Z_ARRVAL_PP(zval_pp) (**zval). value.ht

6. 變數的符號表與範圍

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

[php] view plaincopy

  1. $var = 'Hello World'; ?>

$var的變數名會儲存在變數符號表中,代表$var的類型和值的zval結構儲存在雜湊表中。核心通過變數符號表與zval地址的雜湊映射,來實現PHP變數的存取。

為什麼要提範圍呢?因為函數內部變數保護。按照範圍PHP的變數分為全域變數和局部變數,每種範圍PHP都會維護一個符號表的HashTable。當在PHP中建立一個函數或類的時候,ZE會建立一個新的符號表,表明函數或類中的變數是局部變數,這樣就實現了局部變數的保護--外部無法訪問函數內部的變數。當建立一個PHP變數的時候,ZE會分配一個zval,並設定相應type和初始值,把這個變數加入當前範圍的符號表,這樣使用者才能使用這個變數。
核心中使用ZEND_SET_SYMBOL來設定變數:

[php] view plaincopy

  1. ZEND_SET_SYMBOL( EG(active_symbol_table), "foo", foo);

查看_zend_executor_globals結構

[php] view plaincopy

  1. Zend/zend_globals.h
  2. struct _zend_executor_globals { //略 HashTable symbol_table;//全域變數的符號表 HashTable *active_symbol_table;//局部變數的符號表 //略 };

在寫PHP擴充時候,可以通過EG宏來訪問PHP的變數符號表。EG(symbol_table)訪問全域範圍的變數符號表,EG(active_symbol_table)訪問當前範圍的變數符號表,局部變數儲存的是指標,在對HashTable進行操作的時候傳遞給相應函數。

為了更好的理解變數的雜湊表與範圍,舉個簡單的例子:

[php] view plaincopy

  1. $temp = 'global'; function test() { $temp = 'active'; } test(); var_dump($temp); ?>

建立函數外的變數$temp,會把這個它加入全域符號表,同時在全域符號表的HashTable中,分配一個字元類型的zval,值為‘global‘。建立函數test內部變數$temp,會把它加入屬於函數test的符號表,分配字元型zval,值為’active' 。

7. PHP擴充中變數操作

建立PHP變數

我們可以在擴充中調用函數MAKE_STD_ZVAL(pzv)來建立一個PHP可調用的變數,MAKE_STD_ZVAL應用到的宏有:

[php] view plaincopy

  1. #define MAKE_STD_ZVAL(zv) ALLOC_ZVAL(zv);INIT_PZVAL(zv) #define ALLOC_ZVAL(z) ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST) #define ZEND_FAST_ALLOC(p, type, fc_type) (p) = (type *) emalloc(sizeof(type)) #define INIT_PZVAL(z) (z)->refcount__gc = 1;(z)->is_ref__gc = 0;

MAKE_STD_ZVAL(foo)展開後得到:

[php] view plaincopy

  1. (foo) = (zval *) emalloc(sizeof(zval)); (foo)->refcount__gc = 1; (foo)->is_ref__gc = 0;

可以看出,MAKE_STD_ZVAL做了三件事:分配記憶體、初始化zval結構中的refcount、is_ref。

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

API Macros for Accessing zval
實現方法
ZVAL_NULL(pvz) Z_TYPE_P(pzv) = IS_NULL
ZVAL_BOOL(pvz) Z_TYPE_P(pzv) = IS_BOOL;
Z_BVAL_P(pzv) = b ? 1 : 0;
ZVAL_TRUE(pvz) ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pvz) ZVAL_BOOL(pzv, 0);
ZVAL_LONG(pvz, l)(l 是值) Z_TYPE_P(pzv) = IS_LONG;Z_LVAL_P(pzv) = l;
ZVAL_DOUBLE(pvz, d) Z_TYPE_P(pzv) = IS_DOUBLE;Z_LVAL_P(pzv) = d;
ZVAL_STRINGL(pvz, str, len, dup) Z_TYPE_P(pzv) = IS_STRING;Z_STRLEN_P(pzv) = len;
if (dup) {
{Z_STRVAL_P(pzv) =estrndup(str, len + 1);}
}else {
{Z_STRVAL_P(pzv) = str;}
}
ZVAL_STRING(pvz, str, len) ZVAL_STRINGL(pzv, str,strlen(str), dup);
ZVAL_RESOURCE(pvz, res) Z_TYPE_P(pzv) = IS_RESOURCE;Z_RESVAL_P(pzv) = 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教程有興趣的朋友有所協助。

  • 相關文章

    聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.