轉載請注明出處http://blog.csdn.net/fanhengguang_php
php內核中使用zval表示一個php變量。
一個zval(zend value 的簡寫)結構可以表示一個任意的php變量,這是整個php內核中最重要的數據結構,本章將會介紹zval的基本概念以及如何使用。
每個zval中存儲了一個變量的值以及變量的類型。 這點非常必要,因為php是一個動態類型的語言,變量的類型是在運行階段確定的,而不是編譯階段。另外變量的類型在zval的聲明周期內是可以改變的,所以一個zval可能開始的時候存儲的是int整型,過一會又變成了字符串string類型。
zval的類型用一個整形來標記(unsigned char)。 它可以有8種值,對應php中的8中變量類型, 變量的值可以通過一個常量的范式IS_TYPE來訪問, 如IS_NULL代表null類型, IS_STRING代表string類型。
zval的實際的值存儲在一個unin結構中,如下:
typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj;} zvalue_value;對于不熟悉unin結構的同學來說:unin定義了多個成員變量, 但是任意時刻只有一個成員變量被使用, 不熟悉的同學可以自行GOOGle
通過zvals的type 標記就可以知道當前union中那個成員正在被使用, 在介紹相關api之前, 我們先簡單看下php支持哪些不同類型的變量,以及是如何存儲的。
IS_NULL
是最簡單的類型, 并不需要存儲任何值,因為null類型只有一個null值,
php通過long lval 和double dval 成員變量來存儲IS_LONG 和IS_DOUBLE類型, 前者表示整型,后者表示浮點型。
對于long型需要注意的是,首先它是一個有符號類型,也就是說既可以存儲正整數也可以存儲負數,但是其對于按位運算不太合適; 其次long型在不同平臺上會有不同長度,對于32位系統為4bytes,對于64位系統長度可能是4或者8bytes。
基于以上原因,你不能依賴特定長度的long型, long型的最大最小值可以通過常量LONG_MAX
和 LONG_MIN
來獲的。 長度可以通過SIZEOF_LONG得到。
double類型用來存儲浮點型,通常為8types,但是你應該意識到這個類型的精度也是有局限的,有時存儲的并不是你想要的結果。
布爾類型通過IS_BOOL
常量標記,其值存儲在long lval
中, 0表示false, 1表示true。 由于布爾類型只有2個值,理論上可以用一個更小的類型如unsigned char 來表示, 但是由于zvalue_value
是一個union, 其占用內存的大小是由最大的成員決定的, 所有這里復用的lval。
字符串Strings(IS_STRING
)類型的值存儲在
可見其包含一個char* 字符串指針,和int型字符串長度。 為保證二進制安全php字符串需要存儲字符串的明確長度, 因為字符串中可能包含NUL(‘/0’)。 但是PHP底層字符串仍是用NUL(‘/0’)結尾的c字符串,這樣的好處是很多現成的庫并不支持顯示傳遞字符串長度參數,當然這樣的話就不是二進制安全的了,如果其中包含NUL字符,將會被截斷。例如很多系統相關函數都是這樣處理的,包括libc中的大部分函數。
字符串長度以bytes計算,是不包括結尾的NUL字符的,"foo"
長度為3,但是其占用了4個bytes, 如果你使用sizeof計算字符串長度,需要記得-1:strlen("foo") == sizeof("foo") -1
而且string長度是用int而不是long型存儲的, 這是一個不幸的歷史問題,這導致字符串的長度最大為2147483647, 超過限制會導致溢出(長度會變成負值).
余下的幾個類型,將會簡單介紹下, 后面會有獨立的章節詳細介紹:
Array 通過IS_ARRAY
標記, 其值存儲在HashTable *ht
成員變量中。 Objects (IS_OBJECT
) 值存儲在 zend_object_value
中, 他包含了一個用來查找這個對象的實際的數據的int object handle 對象句柄和一系列的用于決定這個對象行為的句柄。php 類和對象系統將會在后面介紹。
Resource(IS_RESOURCE
)和 objects類似,頁存儲了一個唯一ID用來查找實際的value。 這個ID存儲在long lval成員變量中,后面有具體章節介紹resource。
總結一下:下表列出了php中的類型以及相應的value的實際存儲位置:
Type tag Storage locationIS_NULL noneIS_BOOL long lvalIS_LONG long lvalIS_DOUBLE double dvalIS_STRING struct { char *val; int len; } strIS_ARRAY HashTable *htIS_OBJECT zend_object_value objIS_RESOURCE long lval我們看下zval 結構體的結構
typedef struct _zval_struct { zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc;} zval;前面已經提到過,zval的成員變量中存儲了變量的值value 和變量的類型,value存儲在我們上面討論過的zvalue_value value;
union中,變量類型存儲在zend_uchar
中。 另外有兩個成員變量以__gc
結尾, 這兩個變量用來進行垃圾回收處理的,這里暫且布標,后面再討論。
在了解了zval結構之后, 你可以編碼使用它了:
zval *zv_ptr = /* ... get zval from somewhere */;if (zv_ptr->type == IS_LONG) { php_雖然上面的代碼是可行的,但是這并不是好的習慣,它直接訪問了zval的成員而不是通過一系列訪問宏:zval *zv_ptr = /* ... */;if (Z_TYPE_P(zv_ptr) == IS_LONG) { php_printf("Zval is a long with value %ld/n", Z_LVAL_P(zv_ptr));} else /* ... */上面的代碼中使用了Z_TYPE_P()
宏來獲取類型標記, 用Z_LVAL_P()
來獲取zval中的long型value。 所有這些存取宏都類似于這樣_P后綴, _PP后綴, 或者沒有后綴。使用哪一種形式取決于你當前是在處理zval, zval* 還是zval**
基本上P后綴的數量和*
的數量是一致的, 這個規則適用于zval**
以內,對于zval***
是沒有特定訪問宏的, 因為其極少用到。
如Z_LVAL
,還有很多其他的訪問宏用于訪問其他類型的value,為了演示他們的用法,我們看下面這個zval打印函數。
運行結果如下:
dump(null); // NULL: nulldump(true); // BOOL: truedump(false); // BOOL: falsedump(42); // LONG: 42dump(4.2); // DOUBLE: 4.2dump("foo"); // STRING: value="foo", length=3dump(fopen(__FILE__, "r")); // RESOURCE: id=???dump(array(1, 2, 3)); // ARRAY: hashtable=0x???dump(new stdClass); // OBJECT: ???這些zval 訪問宏是非常直接了當的: Z_BVAL bools
對應bool類型 Z_LVAL
對應long型, Z_DVAL
對應double類型。 Z_STRVAL
返回實際的字符串測char*
指針,Z_STRLEN
返回字符串長度。資源id通過Z_RESVAL
獲取。數組類型zval的HashTable*
通過Z_ARRVAL
宏獲取。object類型value的獲取暫且不表,因為涉及到有些背景知識。
當你想要訪問zval的value的時候, 你應當使用這些宏定義,而不是直接訪問結構體、聯合體的成員。通過這些宏定義訪問zval的內容可以避免歧義和錯誤,而且可以保證PHP內核升級的兼容性。
上面介紹的一些存取宏,僅僅提供了某些zval結構成員變量的存取方法。可以通過這些訪問對成員變量進行讀寫。對于一些常見的操作來說,通過上面的宏操作是比較復雜的,考慮以下函數,幾年返回一個字符串hello world。
PHP_FUNCTION(hello_world) { Z_TYPE_P(return_value) = IS_STRING; Z_STRVAL_P(return_value) = estrdup("hello world!"); Z_STRLEN_P(return_value) = strlen("hello world!");};/* ... */ PHP_FE(hello_world, NULL)/* ... */執行php -r "echo hello_world();"
, 控制臺輸出hello world。
上線的例子我們設置了return_value
變量, 這是一個由PHP_FUNCTION
提供的zval*
指針。 后面我們會詳細介紹它, 在這里你只需要知道這個值將會是這個函數的返回值,默認情況下它被初始化為IS_NULL
.
通過之前介紹的訪問宏來操作zval非常簡單,但是需要注意的是:你需要單獨去設置zval的類型。僅僅設置內容(通過Z_STRVAL
and Z_STRLEN
here)是不行的。 你總是要注意自己手動設置類型標記。
另外你需要注意大多數情況下zval以及其包含的值的的生命周期會比你操作這個zval的上下文還要長, 有時也有例外,比如你操作一個臨時的zvals。
對于上面的例子意味著return_value
在函數結束后會依然存在(這是顯而易見的, 否則不會有人去使用return_value
了). 所以這里不能使用臨時的變量,例如Z_STRVAL_P(return_value) = "hello world!"
是非法的, 以為字符串hello Word
將會隨著函數退出而消失(對于c語言每個棧上分配的變量都是這樣).
因此我們需要使用estrdup()
拷貝字符串,這將會在堆上創建一個獨立的字符串拷貝, 由于這個字符串是zval的成員,當zval銷毀的時候,需要確保這個字符串也被釋放掉, 這個特性也適用于其他復雜的zval。 例如當你設置一個zval的HashTable*
后,zval將會接管這個變量,當zval被釋放的時候,它也會被隨之釋放掉。對于那些簡單類型例如整型或者double型來說不需要考慮這個問題。
最后,并不是所有的訪問宏都會返回一個成員,Z_BVAL
定義如下:
由于其包含一個類型轉換操作,所以你不能這樣操作Z_BVAL_P(return_value) = 1
除去一些對象操作的宏,這是唯一的例外,其他的訪問宏,可以用來設置value。
實際上你無需考慮字符串的結尾符, 因為設置zval的value是一個常用操作,所以php提供了另外的一些宏來提供這個功能, 通過這些宏你可以同時設定類型標記和zval的值,通過宏來重寫上面的例子:
PHP_FUNCTION(hello_world) { ZVAL_STRINGL(return_value, estrdup("hello world!"), strlen("hello world!"), 0);}由于設置zval值時通常需要對字符串進行拷貝,你可以直接使用ZVAL_STRINGL
的最后一個參數完成這個操作,如果傳遞0則直接使用這個字符串, 如果傳遞1,字符串會通過estrndup()
進行拷貝, 這樣我們的例子可以重寫如下:
更進一步,我們無需手動計算字符串長度strlen, 可以使用ZVAL_STRING
后面沒有L
如果你知道字符串長度(因為字符串長度可能會傳遞給你), 你應該使用ZVAL_STRINGL
宏來保證二進制安全。如果你不知道字符串長度(或者知道字符串中不包含NUL字節)你可以使用ZVAL_STRING
代替。
除了ZVAL_STRING(L)
之外,還有一些用于設置zval 值的宏,如下:
注意:這些宏只負責設置zval的值, 不會銷毀之前的已經存在的值,對于return_value
是沒問題的,因為它被初始化為IS_NULL
類型(沒有任何value需要被釋放). 但是其他情況下你需要先釋放掉老的value值,具體方法下節介紹。
新聞熱點
疑難解答
圖片精選