亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > 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值,具體方法下節介紹。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
91精品成人久久| 国产美女直播视频一区| 91精品国产综合久久久久久久久| 日本一区二区三区四区视频| 亚洲欧美国产日韩中文字幕| 亚洲视频综合网| 亚洲欧美国产精品| 亚洲欧美日韩国产精品| 色噜噜久久综合伊人一本| 富二代精品短视频| 亚洲黄色www网站| 日韩欧美精品网站| 清纯唯美亚洲综合| 亚洲国产精品va| 日韩欧中文字幕| www国产精品视频| 亚洲人成在线观看| 2019日本中文字幕| 国产日韩精品视频| 欧美日韩中文字幕在线视频| 亚洲精品色婷婷福利天堂| 色综合视频网站| 亚洲一区二区三区在线视频| 91久久精品一区| 91国产美女视频| 亚洲黄色免费三级| 久久天天躁狠狠躁夜夜爽蜜月| 亚洲欧美制服丝袜| 在线性视频日韩欧美| 日韩美女写真福利在线观看| 欧美大荫蒂xxx| 国产日韩综合一区二区性色av| 亚洲第一区在线观看| 久久视频在线直播| 欧美视频国产精品| 日韩欧美亚洲国产一区| 热久久美女精品天天吊色| 国产成人精品在线| 中文字幕日韩专区| 91久久精品国产| 亚洲成人网在线| 久久久久久久一区二区| 精品视频www| 亚洲中国色老太| 欧美极品少妇xxxxⅹ裸体艺术| 国产精品一区专区欧美日韩| 欧美成人精品h版在线观看| 亚洲天堂男人的天堂| 国产99久久精品一区二区| 亚洲精品v欧美精品v日韩精品| 国产免费观看久久黄| 日韩精品在线视频美女| 亚洲国产日韩欧美在线图片| 性欧美激情精品| 欧美性猛交99久久久久99按摩| 国产盗摄xxxx视频xxx69| 日韩在线国产精品| 97国产精品免费视频| 国产一区二区香蕉| 久久久免费观看视频| 欧美大片第1页| 欧美成人免费在线视频| 91精品国产91久久久| 色播久久人人爽人人爽人人片视av| 亚洲欧美变态国产另类| 在线精品视频视频中文字幕| 国产成人亚洲综合青青| 欧美大肥婆大肥bbbbb| 91网在线免费观看| 国产精品日韩精品| 奇米一区二区三区四区久久| 亚洲美女av网站| 国产日韩换脸av一区在线观看| 日韩美女主播视频| 国产精品精品久久久| 欧美日韩亚洲激情| 久操成人在线视频| 91精品久久久久久综合乱菊| 亚洲色图欧美制服丝袜另类第一页| 久久综合久中文字幕青草| 国产欧美日韩丝袜精品一区| 亚洲精品国产电影| 国产精品网站大全| 色综合导航网站| 欧美成人免费va影院高清| 日韩成人中文字幕在线观看| 国产精品白丝av嫩草影院| 中文字幕在线日韩| 久久久亚洲天堂| 在线看欧美日韩| 欧美中文字幕第一页| 日韩欧美一区二区在线| 日韩高清电影免费观看完整| 精品香蕉一区二区三区| 国产免费成人av| 日韩精品中文字| 亚洲激情在线观看| 亚洲成人黄色在线观看| 精品国产一区二区三区久久狼黑人| 国产精品久久久久久久久久小说| 一区二区三区无码高清视频| 久久久久国色av免费观看性色| 性色av一区二区三区在线观看| 欧美激情性做爰免费视频| 精品久久久免费| 亲爱的老师9免费观看全集电视剧| 日韩一中文字幕| 精品国模在线视频| 国产美女精品视频免费观看| 亚洲少妇激情视频| 国产精品日韩在线观看| 中国人与牲禽动交精品| 亚洲国产成人久久综合一区| 一区二区福利视频| 91精品国产网站| 亚洲影影院av| 国产亚洲精品久久久久久| 久久亚洲精品视频| 国产色婷婷国产综合在线理论片a| 国产精品国产自产拍高清av水多| 亚洲图片欧洲图片av| 国产精品96久久久久久| 国产成人精品久久亚洲高清不卡| 久久久久久一区二区三区| 国内成人精品视频| 亚洲激情视频网| 成人两性免费视频| 九九精品视频在线观看| 亚洲尤物视频网| 成人444kkkk在线观看| 国产视频精品久久久| 精品亚洲一区二区三区在线观看| 精品露脸国产偷人在视频| 国产精品久久久久影院日本| 亚洲一区亚洲二区| 国产丝袜一区二区| 中文字幕最新精品| 九九综合九九综合| 欧美高清激情视频| 国产亚洲欧美日韩一区二区| 久久久成人的性感天堂| 亚洲成人精品视频在线观看| 777国产偷窥盗摄精品视频| 国产不卡av在线免费观看| 国产+人+亚洲| 国产精品丝袜白浆摸在线| 成人免费黄色网| 欧美日韩午夜激情| 色噜噜狠狠色综合网图区| 色悠悠久久88| 中文.日本.精品| 欧美精品免费在线| 欧美日韩亚洲激情| 久久频这里精品99香蕉| 欧美午夜精品久久久久久浪潮| 日韩在线观看免费av| 国产成人亚洲综合| 91在线精品视频| 中文字幕日韩电影| 国产主播欧美精品| 国产人妖伪娘一区91| 九九久久综合网站| 日产精品99久久久久久| 亚洲一区二区三区毛片|