国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > PHP > 正文

PHP內核原理(一)Zvals基本結構

2019-11-08 00:57:54
字體:
來源:轉載
供稿:網友

轉載請注明出處http://blog.csdn.net/fanhengguang_php

Zvals 基本結構

php內核中使用zval表示一個php變量。

一個zval(zend value 的簡寫)結構可以表示一個任意的php變量,這是整個php內核中最重要的數據結構,本章將會介紹zval的基本概念以及如何使用。

Types and values

每個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_MAXLONG_MIN來獲的。 長度可以通過SIZEOF_LONG得到。

double類型用來存儲浮點型,通常為8types,但是你應該意識到這個類型的精度也是有局限的,有時存儲的并不是你想要的結果。

布爾類型通過IS_BOOL常量標記,其值存儲在long lval中, 0表示false, 1表示true。 由于布爾類型只有2個值,理論上可以用一個更小的類型如unsigned char 來表示, 但是由于zvalue_value是一個union, 其占用內存的大小是由最大的成員決定的, 所有這里復用的lval。

字符串Strings(IS_STRING)類型的值存儲在

struct { char *val; int len; } str;

可見其包含一個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**

zval zv;zval *zv_ptr;zval **zv_ptr_ptr;zval ***zv_ptr_ptr_ptr;Z_TYPE(zv); // = zv.typeZ_TYPE_P(zv_ptr); // = zv_ptr->typeZ_TYPE_PP(zv_ptr_ptr); // = (*zv_ptr_ptr)->typeZ_TYPE_PP(*zv_ptr_ptr_ptr); // = (**zv_ptr_ptr_ptr)->type

基本上P后綴的數量和*的數量是一致的, 這個規則適用于zval**以內,對于zval***是沒有特定訪問宏的, 因為其極少用到。

Z_LVAL,還有很多其他的訪問宏用于訪問其他類型的value,為了演示他們的用法,我們看下面這個zval打印函數。

PHP_FUNCTION(dump){ zval *zv_ptr; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zv_ptr) == FAILURE) { return; } switch (Z_TYPE_P(zv_ptr)) { case IS_NULL: php_printf("NULL: null/n"); break; case IS_BOOL: if (Z_BVAL_P(zv_ptr)) { php_printf("BOOL: true/n"); } else { php_printf("BOOL: false/n"); } break; case IS_LONG: php_printf("LONG: %ld/n", Z_LVAL_P(zv_ptr)); break; case IS_DOUBLE: php_printf("DOUBLE: %g/n", Z_DVAL_P(zv_ptr)); break; case IS_STRING: php_printf("STRING: value=/""); PHPWRITE(Z_STRVAL_P(zv_ptr), Z_STRLEN_P(zv_ptr)); php_printf("/", length=%d/n", Z_STRLEN_P(zv_ptr)); break; case IS_RESOURCE: php_printf("RESOURCE: id=%ld/n", Z_RESVAL_P(zv_ptr)); break; case IS_ARRAY: php_printf("ARRAY: hashtable=%p/n", Z_ARRVAL_P(zv_ptr)); break; case IS_OBJECT: php_printf("OBJECT: ???/n"); break; }}const zend_function_entry funcs[] = { PHP_FE(dump, NULL) PHP_FE_END};

運行結果如下:

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 value的設置

上面介紹的一些存取宏,僅僅提供了某些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定義如下:

#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)

由于其包含一個類型轉換操作,所以你不能這樣操作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()進行拷貝, 這樣我們的例子可以重寫如下:

PHP_FUNCTION(hello_world) { ZVAL_STRINGL(return_value, "hello world!", strlen("hello world!"), 1);}

更進一步,我們無需手動計算字符串長度strlen, 可以使用ZVAL_STRING 后面沒有L

PHP_FUNCTION(hello_world) { ZVAL_STRING(return_value, "hello world!", 1);}

如果你知道字符串長度(因為字符串長度可能會傳遞給你), 你應該使用ZVAL_STRINGL宏來保證二進制安全。如果你不知道字符串長度(或者知道字符串中不包含NUL字節)你可以使用ZVAL_STRING代替。

除了ZVAL_STRING(L)之外,還有一些用于設置zval 值的宏,如下:

ZVAL_NULL(return_value);ZVAL_BOOL(return_value, 0);ZVAL_BOOL(return_value, 1);/* or better */ZVAL_FALSE(return_value);ZVAL_TRUE(return_value);ZVAL_LONG(return_value, 42);ZVAL_DOUBLE(return_value, 4.2);ZVAL_RESOURCE(return_value, resource_id);ZVAL_EMPTY_STRING(return_value);/* = ZVAL_STRING(return_value, "", 1); */ZVAL_STRING(return_value, "string", 1);/* = ZVAL_STRING(return_value, estrdup("string"), 0); */ZVAL_STRINGL(return_value, "nul/0string", 10, 1);/* = ZVAL_STRINGL(return_value, estrndup("nul/0string", 10), 10, 0); */

注意:這些宏只負責設置zval的值, 不會銷毀之前的已經存在的值,對于return_value是沒問題的,因為它被初始化為IS_NULL類型(沒有任何value需要被釋放). 但是其他情況下你需要先釋放掉老的value值,具體方法下節介紹。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 卢龙县| 正镶白旗| 新巴尔虎左旗| 青铜峡市| 综艺| 公安县| 竹北市| 彭阳县| 鹤山市| 永福县| 麻城市| 大厂| 阿鲁科尔沁旗| 贵溪市| 南郑县| 剑阁县| 黔江区| 闽侯县| 抚远县| 柳州市| 天等县| 麻城市| 福海县| 光山县| 湛江市| 井研县| 墨江| 淮滨县| 类乌齐县| 如皋市| 宜丰县| 湘潭县| 临桂县| 连州市| 西华县| 宜州市| 岐山县| 旅游| 旅游| 城固县| 水城县|