1. 程式人生 > >《PHP擴充套件開發及核心應用》學習筆記(二)

《PHP擴充套件開發及核心應用》學習筆記(二)

二、PHP變數在核心中的實現

強型別的程式語言中,我們必須在使用變數先宣告(定義)變數的型別和名稱。
而PHP屬於弱型別的程式語言,PHP 會根據變數的值,自動把變數轉換為正確的資料型別。

程式語言

1. 變數的型別

PHP中,一共有8種資料型別:

  • 包括4中標量資料型別:即boolean(布林型別),integer(整型),float/double(浮點型),string(字串型);
  • 兩種複合資料型別:即array(陣列),object(物件);
  • 兩種特殊的資料型別:即resource(資源),null(無,空白);
常量名稱 註釋
IS_NULL 第一次使用的變數如果沒有初始化過,則會自動的被賦予這個常量,當然我們也可以在PHP語言中通過null這個常量來給予變數null型別的值。 這個型別的值只有一個 ,就是NULL,它和0與false是不同的。
IS_BOOL 布林型別的變數有兩個值,true/false。在PHP語言中,while、if等語句會自動的把表示式的值轉成這個型別的。
IS_LONG PHP語言中的整型,在核心中是通過所在作業系統的signed long資料型別來表示的。 在最常見的32位作業系統中,它可以儲存從-2147483648 到 +2147483647範圍內的任一整數。 有一點需要注意的是,如果PHP語言中的整型變數超出最大值或者最小值,它並不會直接溢位, 而是會被核心轉換成IS_DOUBLE型別的值然後再參與計算。 再者,因為使用了signed long來作為載體,所以這也就解釋了為什麼PHP語言中的整型資料都是帶符號的了。
IS_DOUBLE PHP中的浮點資料是通過C語言中的signed double型變數來儲存的, 這最終取決與所在作業系統的浮點型實現。 我們做為程式猿,應該知道計算機是無法精準的表示浮點數的, 而是採用了科學計數法來儲存某個精度的浮點數。 用科學計數法,計算機只用8位便可以儲存2.225x10(-308)~~1.798x10308之間的浮點數。 用計算機來處理浮點數簡直就是一場噩夢,十進位制的0.5專成二進位制是0.1, 0.8轉換後是0.1100110011…。 但是當我們從二進位制轉換回來的時候,往往會發現並不能得到0.8。 我們用1除以3這個例子來解釋這個現象:1/3=0.3333333333…,它是一個無限迴圈小數, 但是計算機可能只能精確儲存到0.333333,當我們再乘以三時, 其實計算機計算的數是0.333333*3=0.999999,而不是我們平時數學中所期盼的1.0。
IS_STRING PHP中最常用的資料型別——字串,在記憶體中的儲存和C差不多, 就是一塊能夠放下這個變數所有字元的記憶體,並且在這個變數的zval實現裡會儲存著指向這塊記憶體的指標。 與C不同的是,PHP核心還同時在zval結構裡儲存著這個字串的實際長度, 這個設計使PHP可以在字串中嵌入‘\0’字元,也使PHP的字串是二進位制安全的, 可以安全的儲存二進位制資料!本著艱苦樸素的作風,核心只會為字串申請它長度+1的記憶體, 最後一個位元組儲存的是‘\0’字元,所以在不需要二進位制安全操作的時候, 我們可以像通常C語言的方式那樣來使用它。
IS_ARRAY 陣列是一個非常特殊的資料型別,它唯一的功能就是聚集別的變數。 在C語言中,一個數組只能承載一種型別的資料,而PHP語言中的陣列則靈活的多, 它可以承載任意型別的資料,這一切都是HashTable的功勞, 每個HashTable中的元素都有兩部分組成:索引與值, 每個元素的值都是一個獨立的zval(確切的說應該是指向某個zval的指標)。
IS_OBJECT 和陣列一樣,物件也是用來儲存複合資料的,但是與陣列不同的是, 物件還需要儲存以下資訊:方法、訪問許可權、類常量以及其它的處理邏輯。 相對與zend engine V1,V2中的物件實現已經被徹底修改, 所以我們PHP擴充套件開發者如果需要自己的擴充套件支援面向物件的工作方式, 則應該對PHP5和PHP4分別對待!
IS_RESOURCE 有一些資料的內容可能無法直接呈現給PHP使用者的, 比如與某臺mysql伺服器的連結,或者直接呈現出來也沒有什麼意義。 但使用者還需要這類資料,因此PHP中提供了一種名為Resource(資源)的資料型別。

來源核心zend_operators.h中的一段程式碼:

#define convert_to_explicit_type(pzv, type)
    do {
        switch (type) {
            case IS_NULL:
                convert_to_null(pzv);
                break;
            case IS_LONG:
                convert_to_long(pzv);
                break;
            case IS_DOUBLE:
                convert_to_double(pzv);
                break;
            case IS_BOOL:
                convert_to_boolean(pzv); 
                break;
            case IS_ARRAY:
                convert_to_array(pzv);
                break;
            case IS_OBJECT:
                convert_to_object(pzv);
                break;
            case IS_STRING:
                convert_to_string(pzv);
                break;
            default:
                assert(0);
                break;
        }
    } while (0);

2. 變數的值

PHP有8中變數型別,每種變數型別中的值,對應的不同的儲存方法。

//操作整數的
#define Z_LVAL(zval)			(zval).value.lval
#define Z_LVAL_P(zval_p)		Z_LVAL(*zval_p)
#define Z_LVAL_PP(zval_pp)		Z_LVAL(**zval_pp)

//操作IS_BOOL布林型的
#define Z_BVAL(zval)			((zend_bool)(zval).value.lval)
#define Z_BVAL_P(zval_p)		Z_BVAL(*zval_p)
#define Z_BVAL_PP(zval_pp)		Z_BVAL(**zval_pp)

//操作浮點數的
#define Z_DVAL(zval)			(zval).value.dval
#define Z_DVAL_P(zval_p)		Z_DVAL(*zval_p)
#define Z_DVAL_PP(zval_pp)		Z_DVAL(**zval_pp)

//操作字串的值和長度的
#define Z_STRVAL(zval)			(zval).value.str.val
#define Z_STRVAL_P(zval_p)		Z_STRVAL(*zval_p)
#define Z_STRVAL_PP(zval_pp)		Z_STRVAL(**zval_pp)

#define Z_STRLEN(zval)			(zval).value.str.len
#define Z_STRLEN_P(zval_p)		Z_STRLEN(*zval_p)
#define Z_STRLEN_PP(zval_pp)		Z_STRLEN(**zval_pp)

#define Z_ARRVAL(zval)			(zval).value.ht
#define Z_ARRVAL_P(zval_p)		Z_ARRVAL(*zval_p)
#define Z_ARRVAL_PP(zval_pp)		Z_ARRVAL(**zval_pp)

//操作物件的
#define Z_OBJVAL(zval)			(zval).value.obj
#define Z_OBJVAL_P(zval_p)		Z_OBJVAL(*zval_p)
#define Z_OBJVAL_PP(zval_pp)		Z_OBJVAL(**zval_pp)

#define Z_OBJ_HANDLE(zval)		Z_OBJVAL(zval).handle
#define Z_OBJ_HANDLE_P(zval_p)		Z_OBJ_HANDLE(*zval_p)
#define Z_OBJ_HANDLE_PP(zval_p)		Z_OBJ_HANDLE(**zval_p)

#define Z_OBJ_HT(zval)			Z_OBJVAL(zval).handlers
#define Z_OBJ_HT_P(zval_p)		Z_OBJ_HT(*zval_p)
#define Z_OBJ_HT_PP(zval_p)		Z_OBJ_HT(**zval_p)

#define Z_OBJCE(zval)			zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJCE_P(zval_p)		Z_OBJCE(*zval_p)
#define Z_OBJCE_PP(zval_pp)		Z_OBJCE(**zval_pp)

#define Z_OBJPROP(zval)			Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJPROP_P(zval_p)		Z_OBJPROP(*zval_p)
#define Z_OBJPROP_PP(zval_pp)		Z_OBJPROP(**zval_pp)

#define Z_OBJ_HANDLER(zval, hf) 	Z_OBJ_HT((zval))->hf
#define Z_OBJ_HANDLER_P(zval_p, h)	Z_OBJ_HANDLER(*zval_p, h)
#define Z_OBJ_HANDLER_PP(zval_p, h)		Z_OBJ_HANDLER(**zval_p, h)

#define Z_OBJDEBUG(zval,is_tmp)		(Z_OBJ_HANDLER((zval),get_debug_info)?	\
						Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&is_tmp TSRMLS_CC): \
						(is_tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL)) 
#define Z_OBJDEBUG_P(zval_p,is_tmp)	Z_OBJDEBUG(*zval_p,is_tmp) 
#define Z_OBJDEBUG_PP(zval_pp,is_tmp)	Z_OBJDEBUG(**zval_pp,is_tmp)

//操作資源的
#define Z_RESVAL(zval)			(zval).value.lval
#define Z_RESVAL_P(zval_p)		Z_RESVAL(*zval_p)
#define Z_RESVAL_PP(zval_pp)		Z_RESVAL(**zval_pp)

3.建立PHP變數

PHP變數在核心中,是使用zval結構來實現的。

新巨集 其它巨集的實現方法
ZVAL_NULL(pvz); (注意這個Z和VAL之間沒有下劃線!) Z_TYPE_P(pzv) = IS_NULL;(IS_NULL型不用賦值,因為這個型別只有一個值就是null,_)
ZVAL_BOOL(pzv, b); (將pzv所指的zval設定為IS_BOOL型別,值是b) Z_TYPE_P(pzv) = IS_BOOL;
Z_BVAL_P(pzv) = b ? 1 : 0;
ZVAL_TRUE(pzv); (將pzv所指的zval設定為IS_BOOL型別,值是true) ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pzv); (將pzv所指的zval設定為IS_BOOL型別,值是false) ZVAL_BOOL(pzv, 0);
ZVAL_LONG(pzv, l); (將pzv所指的zval設定為IS_LONG型別,值是l) Z_TYPE_P(pzv) = IS_LONG;
Z_LVAL_P(pzv) = l;
ZVAL_DOUBLE(pzv, d); (將pzv所指的zval設定為IS_DOUBLE型別,值是d) Z_TYPE_P(pzv) = IS_DOUBLE;
Z_DVAL_P(pzv) = d;
ZVAL_STRINGL(pzv,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(pzv, str, dup); ZVAL_STRINGL(pzv, str,strlen(str), dup);
ZVAL_RESOURCE(pzv, 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。

《撫琴居》上的一篇文章說這項特性將會在你僅僅需要建立一個變數並將其指向一個已經由 Zend 內部資料記憶體時變得很有用。

ZVAL_STRINGL與ZVAL_STRING的區別

如果你想在某一位置擷取該字串或已經知道了這個字串的長度, 那麼可以使用巨集 ZVAL_STRINGL(zval, string, length, duplicate) ,它顯式的指定字串長度, 而不是使用strlen()。這個巨集該字串長度作為引數。但它是二進位制安全的,而且速度也比ZVAL_STRING快,因為少了個strlen。

ZVAL_RESOURCE約等於ZVAL_LONG

上一節中我們說過PHP中的資源型別的值其實就是一個整數,所以ZVAL_RESOURCE和ZVAL_LONG的工作差不多, 只不過它會把zval的型別設定為 IS_RESOURCE。