PHP內(nèi)核探索 —— 變量的類型:PHP弱類型變量特性是如何實(shí)現(xiàn)?
所有的編程語言都要提供一種數(shù)據(jù)的存儲(chǔ)與檢索機(jī)制,PHP也不例外。其它語言大都需要在使用變量之前先定義,并且它的類型也是無法再次改變的,而PHP卻允許程序猿自由的使用變量而無須提前定義,甚至可以隨時(shí)隨意的對(duì)已存在的變量轉(zhuǎn)換成其它任何PHP支持的數(shù)據(jù)類型。在程序在運(yùn)行的時(shí)候,PHP還會(huì)自動(dòng)的根據(jù)需求轉(zhuǎn)換變量的類型。
如果你用過PHP,肯定體驗(yàn)過PHP的弱類型的變量體系。眾所周知,PHP引擎是用C寫的,而C確實(shí)一種強(qiáng)類型的編程語言,PHP內(nèi)核中是如何用C來實(shí)現(xiàn)自己的這種弱類型特性的?下面談?wù)勛兞康念愋汀?/p>
PHP在內(nèi)核中是通過zval這個(gè)結(jié)構(gòu)體來存儲(chǔ)變量的,它的定義在Zend/zend.h文件里,簡(jiǎn)短精煉,只有四個(gè)成員組成:
struct _zval_struct {zvalue_value value;/* 變量的值 */zend_uint refcount__gc;zend_uchar type;/* 變量當(dāng)前的數(shù)據(jù)類型 */zend_uchar is_ref__gc;};typedef struct _zval_struct zval;//在Zend/zend_types.h里定義的:typedef unsigned int zend_uint;typedef unsigned char zend_uchar;
zval里的refcout__gc是zend_uint類型,也就是unsinged int型,is_ref__gc和type則是unsigned char型的。保存變量值的value則是zvalue_value類型(PHP5),它是一個(gè)Union,同樣定義在了Zend/zend.h文件里:
typedef union _zvalue_value {long lval;/* long value */double dval;/* double value */struct {char *val;int len;} str;HashTable *ht;/* hash table value */zend_object_value obj;} zvalue_value;
在以上實(shí)現(xiàn)的基礎(chǔ)上,PHP語言得以實(shí)現(xiàn)了8種數(shù)據(jù)類型,這些數(shù)據(jù)類型在內(nèi)核中的分別對(duì)應(yīng)于特定的常量,它們分別是:
常量名稱:IS_NULL第一次使用的變量如果沒有初始化過,則會(huì)自動(dòng)的賦予這個(gè)變量,當(dāng)然我們也可以在PHP語言中通過null這個(gè)常量來給予變量null類型的值。 這個(gè)類型的值只有一個(gè) ,就是NULL,它和0與false是不同的。IS_BOOL布爾類型的變量有兩個(gè)值,true或者false。在PHP語言中,while、if等語句會(huì)自動(dòng)的把表達(dá)式的值轉(zhuǎn)成這個(gè)類型的。IS_LONGPHP語言中的整型,在內(nèi)核中是通過所在操作系統(tǒng)的singed long數(shù)據(jù)類型來表示的。在最常見的32位操作系統(tǒng)中,它可以存儲(chǔ)從-2147483648 到 +2147483647范圍內(nèi)的任一整數(shù)。有一點(diǎn)需要注意的是,如果PHP語言中的整型變量超出最大值或者最小值,它并不會(huì)直接溢出,而是會(huì)被內(nèi)核轉(zhuǎn)換成IS_DOUBLE類型的值然后再參與計(jì)算。再者,因?yàn)槭褂昧藄inged long來作為載體,所以這也就解釋了為什么PHP語言中的整型數(shù)據(jù)都是帶符號(hào)的了。$a=2147483647;$a++;echo $a;//會(huì)正確的輸出2147483648;IS_DOUBLEPHP中的浮點(diǎn)數(shù)據(jù)是通過C語言中的singed double型變量來存儲(chǔ)的,這最終取決與所在操作系統(tǒng)的浮點(diǎn)型實(shí)現(xiàn)。 我們做為程序猿,應(yīng)該知道計(jì)算機(jī)是無法精準(zhǔn)的表示浮點(diǎn)數(shù)的,而是采用了科學(xué)計(jì)數(shù)法來保存某個(gè)精度的浮點(diǎn)數(shù)。用科學(xué)計(jì)數(shù)法,計(jì)算機(jī)只用8位便可以保存2.225x10^(-308)1.798x10^308之間的浮點(diǎn)數(shù)。用計(jì)算機(jī)來處理浮點(diǎn)數(shù)簡(jiǎn)直就是一場(chǎng)噩夢(mèng),十進(jìn)制的0.5專成二進(jìn)制是0.1,0.8轉(zhuǎn)換后是0.1100110011....。但是當(dāng)我們從二進(jìn)制轉(zhuǎn)換回來的時(shí)候,往往會(huì)發(fā)現(xiàn)并不能得到0.8。我們用1除以3這個(gè)例子來解釋這個(gè)現(xiàn)象:1/3=0.3333333333.....,它是一個(gè)無限循環(huán)小數(shù),但是計(jì)算機(jī)可能只能精確存儲(chǔ)到0.333333,當(dāng)我們?cè)俪艘匀龝r(shí),其實(shí)計(jì)算機(jī)計(jì)算的數(shù)是0.333333*3=0.999999,而不是我們平時(shí)數(shù)學(xué)中所期盼的1.0.IS_STRINGPHP中最常用的數(shù)據(jù)類型——字符串,在內(nèi)存中的存儲(chǔ)和C差不多,就是一塊能夠放下這個(gè)變量所有字符的內(nèi)存,并且在這個(gè)變量的zval實(shí)現(xiàn)里會(huì)保存著指向這塊內(nèi)存的指針。與C不同的是,PHP內(nèi)核還同時(shí)在zval結(jié)構(gòu)里保存著這個(gè)字符串的實(shí)際長(zhǎng)度,這個(gè)設(shè)計(jì)使PHP可以在字符串中嵌入‘0’字符,也使PHP的字符串是二進(jìn)制安全的,可以安全的存儲(chǔ)二進(jìn)制數(shù)據(jù)!本著艱苦樸素的作風(fēng),內(nèi)核只會(huì)為字符串申請(qǐng)它長(zhǎng)度+1的內(nèi)存,最后一個(gè)字節(jié)存儲(chǔ)的是‘0’字符,所以在不需要二進(jìn)制安全操作的時(shí)候,我們可以像通常C語言的概念那樣來使用它。IS_ARRAY數(shù)組是一個(gè)非常特殊的數(shù)據(jù)類型,它唯一的功能就是包含別的變量。在C語言中,一個(gè)數(shù)組只能承載一種類型的數(shù)據(jù),而PHP語言中的數(shù)組則靈活的多,它可以承載任意類型的數(shù)據(jù),這一切都是HashTable的功勞,每個(gè)HashTable中的元素都有兩部分組成:索引與值,每個(gè)元素的值都是一個(gè)獨(dú)立的zval(確切的說應(yīng)該是指向某個(gè)zval的指針)。IS_OBJECT和數(shù)組一樣,對(duì)象也是用來存儲(chǔ)復(fù)合數(shù)據(jù)的,但是與數(shù)組不同的是,對(duì)象還需要保存以下信息:方法、訪問權(quán)限、類常量以及其它的處理邏輯。相對(duì)與zend engine V1,V2中的對(duì)象實(shí)現(xiàn)已經(jīng)被徹底修改,所以我們PHP擴(kuò)展開發(fā)者如果需要自己的擴(kuò)展支持面向?qū)ο蟮墓ぷ鞣绞?,則應(yīng)該對(duì)PHP5和PHP4分別對(duì)待!IS_RESOURCE有一些數(shù)據(jù)的內(nèi)容可能無法直接呈現(xiàn)給PHP用戶的,比如與某臺(tái)mysql服務(wù)器的鏈接,或者直接呈現(xiàn)出來也沒有什么意義。但用戶還需要這類數(shù)據(jù),因此PHP中提供了一種名為Resource(資源)的數(shù)據(jù)類型。有關(guān)這個(gè)數(shù)據(jù)類型的事宜將在第九章中介紹,現(xiàn)在我們只要知道有這么一種數(shù)據(jù)類型就行了。
zval結(jié)構(gòu)體里的type成員的值便是以上某個(gè)IS_*常量之一。內(nèi)核通過檢測(cè)變量的這個(gè)成員值來知道他是什么類型的數(shù)據(jù)并做相應(yīng)的后續(xù)處理。
如果要我們檢測(cè)一個(gè)變量的類型,最直接的辦法便是去讀取它的type成員的值:
void describe_zval(zval *foo){if (foo->type == IS_NULL){php_printf('這個(gè)變量的數(shù)據(jù)類型是: NULL'); } else {php_printf('這個(gè)變量的數(shù)據(jù)類型不是NULL,這種數(shù)據(jù)類型對(duì)應(yīng)的數(shù)字是: %d', foo->type); }}
上述做法看起來沒有錯(cuò)誤,但它是一種被強(qiáng)烈禁止一種做法!
PHP內(nèi)核以后可能會(huì)修改變量的實(shí)現(xiàn)方式,所以檢測(cè)type的方法可能在以后就不能用了。為了解決這個(gè)兼容問題,zend頭文件中定義了大量的宏,供我們檢測(cè)、操作變量使用,使用這些宏不但讓我們的程序更易讀,還具有更好的兼容性。這里我們用Z_TYPE_P()宏來改寫上面那個(gè)程序。
void describe_zval(zval *foo){ if ( Z_TYPE_P(foo) == IS_NULL ) {php_printf('這個(gè)變量的數(shù)據(jù)類型是: NULL'); } else {php_printf('這個(gè)變量的數(shù)據(jù)類型不是NULL,這種數(shù)據(jù)類型對(duì)應(yīng)的數(shù)字是: %d', foo->type); }}
php_printf()函數(shù)是內(nèi)核對(duì)printf()函數(shù)的一層封裝,我們可以像使用printf()函數(shù)那樣使用它。
以_P一個(gè)p結(jié)尾的宏的參數(shù)大多是*zval型變量,此外獲取變量類型的宏還有兩個(gè),分別是Z_TYPE和Z_TYPE_PP,前者的參數(shù)是zval型,而后者的參數(shù)則是**zval。這樣我們便可以猜測(cè)一下php內(nèi)核是如何實(shí)現(xiàn)gettype這個(gè)函數(shù)了,代碼如下:
//開始定義php語言中的函數(shù)gettypePHP_FUNCTION(gettype){//這個(gè)arg間接指向就是我們傳給gettype函數(shù)的參數(shù)。是一個(gè)zval**結(jié)構(gòu)//所以我們要對(duì)他使用__PP后綴的宏。zval **arg;//這個(gè)if的操作主要是讓arg指向參數(shù)~if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 'Z', &arg) == FAILURE) {return;}//調(diào)用Z_TYPE_PP宏來獲取arg指向zval的類型。//然后是一個(gè)switch結(jié)構(gòu),RETVAL_STRING宏代表這gettype函數(shù)返回的字符串類型的值switch (Z_TYPE_PP(arg)) {case IS_NULL:RETVAL_STRING('NULL', 1);break;case IS_BOOL:RETVAL_STRING('boolean', 1);break;case IS_LONG:RETVAL_STRING('integer', 1);break;case IS_DOUBLE:RETVAL_STRING('double', 1);break;case IS_STRING:RETVAL_STRING('string', 1);break;case IS_ARRAY:RETVAL_STRING('array', 1);break;case IS_OBJECT:RETVAL_STRING('object', 1);break;case IS_RESOURCE:{char *type_name;type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC);if (type_name) {RETVAL_STRING('resource', 1);break;}}default:RETVAL_STRING('unknown type', 1);}}
以上三個(gè)宏的定義在Zend/zend_operators.h里,定義分別是:
#define Z_TYPE(zval) (zval).type #define Z_TYPE_P(zval_p) Z_TYPE(*zval_p) #define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)
相關(guān)文章:
1. IntelliJ IDEA創(chuàng)建web項(xiàng)目的方法2. Python寫捕魚達(dá)人的游戲?qū)崿F(xiàn)3. 利用Python實(shí)現(xiàn)Excel的文件間的數(shù)據(jù)匹配功能4. Python多線程實(shí)現(xiàn)支付模擬請(qǐng)求過程解析5. python中的bool數(shù)組取反案例6. Python Request類源碼實(shí)現(xiàn)方法及原理解析7. python numpy中setdiff1d的用法說明8. python基礎(chǔ)之匿名函數(shù)詳解9. HTTP協(xié)議常用的請(qǐng)求頭和響應(yīng)頭響應(yīng)詳解說明(學(xué)習(xí))10. Python趣味挑戰(zhàn)之turtle庫繪畫飄落的銀杏樹
