[翻譯][php擴展開發和嵌入式]第2章-變量的裏裏外外

全部翻譯內容pdf文檔下載地址: http://download.csdn.net/detail/lgg201/5107012

本書目前在github上由laruence(http://www.laruence.com)和walu(http://www.walu.cc)兩位大牛組織翻譯. 該翻譯項目地址爲: https://github.com/walu/phpbook

原書名: <Extending and Embedding PHP>

原作者: Sara Golemon

譯者: goosman.lei(雷果國)

譯者Email: [email protected]

譯者Blog: http://blog.csdn.net/lgg201

變量的裏裏外外

每種編程語言共有的一個特性是存儲和取回信息; php也不例外. 雖然許多語言要求所有的變量都要在使用之前被定義, 並且它們的類型信息是固定的, 然而php允許程序員在使用的時候創建變量, 並且可以存儲任意類型語言能夠表達的信息. 並且還可以在需要的時候自動的轉換變量類型.

因爲你已經使用過用戶空間的php, 因此你應該知道這個概念是"弱類型". 本章, 你將看到這些信息在php的父語言----c(C的類型是嚴格的)中是怎樣編碼的.

當然, 數據的編碼只是一半工作. 爲了保持對所有這些信息片的跟蹤, 每個變量還需要一個標籤和一個容器. 從用戶空間角度來看, 你可以把它們看做是變量名和作用域的概念.

數據類型

php中的數據存儲單位是zval, 也稱作Zend Value. 它是一個只有4個成員的結構體, 在Zend/zend.h中定義, 格式如下:

typedef struct _zval_struct {
	zval_value	value;
	zend_uint	refcount;
	zend_uchar	type;
	zend_uchar	is_ref;
} zval;

我們可以憑直覺猜想到這些成員中多數的基礎存儲類型: unsigned integer的refcount, unsigned character的type和is_ref. 而value成員實際上是一個定義爲union的結構, 在php5中, 它定義如下:

typedef union _zvalue_value {
	long					lval;
	double				dval;
	struct {
		char		*val;
		int		len;
	} 					str;
	HashTable			*ht;
	zend_object_value		obj;
} zvalue_value;

union允許Zend使用一個單一的, 統一的結構來將許多不同類型的數據存儲到一個php變量中.

zend當前定義了下表列出的8種數據類型:


類型值

目的

IS_NULL

這個類型自動的賦值給未初始化的變量,直到它第一次被使用.也可以在用戶空間使用內建的NULL常量進行顯式的賦值.這個變量類型提供了一種特殊的"沒有數據"的類型,它和布爾的FALSE以及整型的0有所不同.

IS_BOOL

布爾變量可以有兩種可能狀態中的一種, TRUE/FALSE.用戶空間控制結構if/while/ternary/for等中間的條件表達式在評估時都會隱式的轉換爲布爾類型.

IS_DOUBLE

浮點數據類型,使用主機系統的signed double數據類型.浮點數並不是以精確的精度存儲的;而是用一個公式表示值的小數部分的有限精度(譯註:浮點數被表示爲3部分:符號,尾數--小數部分,指數.浮點數的值 =符號 *尾數 * 2 ^指數----來自BSD Library Functions Manual: float(3)).這種計數法允許計算機存儲很大範圍的值(正數或負數):8字節就可以表示2.225*10^(-308)1.798*10^(308)範圍內的數字.不幸的是它評估的數字實際的十進制並不能總是像二進制分數一樣乾淨的存儲.例如,十進制表達式0.5轉換爲二進制的精確值是0.1,然而十進制的0.8轉換爲二進制則是無限循環的0.1100110011...,當它轉換回十進制時,因爲無法存儲被丟棄的二進制位將無法恢復.類似的可以想一下將1/3轉換爲十進制的0.333333,兩個值非常相近,但是它不精確,因爲3 * 0.333333並不等於1.0.這個不精確常常會在計算機上處理浮點數時讓人迷惑.(這些範圍限制通常是基於32位平臺的;不同的系統範圍可能不同)

IS_STRING

php中最常見的數據類型是字符串,它的存儲方式符合有經驗的C程序員的預期.分配一塊足夠大去保存字符串中所有的字節/字符的內存,並將指向該字符串的指針保存在宿主zval.

值得注意的是php字符串的長度總是顯式的在zval結構中指出.這就允許字符串包含NULL字節而不被截斷.關於php字符串的這一方面,我們往後稱爲"二進制安全"因爲這樣做使得它可以安全的包含任意類型的二進制數據.

需要注意的是爲一個php字符串分配的內存總量總是最小化的:長度加1.最後的一個字節存放終止的NULL字符,因此不關心二進制安全的函數可以直接傳遞字符串指針.

IS_ARRAY

數組是一種特殊目的的變量,它唯一的功能就是組織其他變量.不像C中的數組概念, php的數組並不是單一類型數據的向量(比如zval arrayofzvals[];).實際上, php的數組是一個複雜的數據桶集合,它的內部是一個HashTable.每個HashTable元素()包含兩個相應的信息片:標籤和數據.php數組的應用場景中,標籤就是關聯數組的key或數值下表,數據就是key指向的變量(zval)

IS_OBJECT

對象擁有數組的多元素數據存儲,此外還增加了方法,訪問修飾符,作用域常量,特殊的事件處理器.作爲一個擴展開發者,構建在php4php5中等價的面向對象代碼是一個很大的挑戰,因爲在Zend引擎1(php4)Zend引擎2(php5)之間,內部的對象模型有非常大的變更.

IS_RESOURCE

有一些數據類型並不能簡單的映射到用戶空間.比如, stdioFILE指針或libmysqlclient的連接句柄,它們不能被簡單的映射爲標量值的數組,那樣做它們就失去了意義.爲了保護用戶空間腳本編寫者不去處理這些問題, php提供了一個泛華的資源數據類型.資源類型的實現細節我們將在第9"資源數據類型"中涉及,現在我們只需要知道有這麼個東西就好了.


上表中的IS_*常量被存儲在zval結構的type元素中, 用來確定在測試變量的值時應該查看value元素中的哪個部分.

最明顯的檢查一個數據的類型的方法如下代碼:

void describe_zval(zval *foo)
{
    if (foo->type == IS_NULL) {
        php_printf("The variable is NULL");
    } else {
        php_printf("The variable is of type %d", foo->type);
    }
}

顯而易見, 但是是錯的.

好吧, 沒有錯, 但確實不是首選做法. Zend頭文件包含了很多的zval訪問宏, 它們是作者期望在測試zval數據時使用的方式. 這樣做主要的原因是避免在引擎的api變更後產生不兼容問題, 不過從另一方面來看這樣做還會使得代碼更加易讀. 下面是相同功能的代碼段, 這一次使用了Z_TYPE_P()宏:

void describe_zval(zval *foo)
{
    if (Z_TYPE_P(foo) == IS_NULL) {
        php_printf("The variable is NULL");
    } else {
        php_printf("The variable is of type %d",
                            Z_TYPE_P(foo));
    }
}

這個宏的_P後綴標識傳遞的參數應該是一級間訪的指針. 還有另外兩個宏Z_TYPE()和Z_TYPE_PP(), 它們期望的參數類型是zval(非指針)和zval **(兩級間訪指針).

注意

在這個例子中使用了一個特殊的輸出函數php_printf(), 它被用於展示數據片. 這個函數語法上等同於stdio的printf函數; 不過它對webserver sapi有特殊的處理, 使用php的輸出緩衝機制提升性能. 你將在第5章"你的第一個擴展"中更多的瞭解這個函數以及它的同族PHPWRITE().

數據值

和類型一樣, zval的值也可以用3個一組的宏檢查. 這些宏總是以Z_開始, 可選的以_P或_PP結尾, 具體依賴於它們的間訪層級.

對於簡單的標量類型, boolean, long, double, 宏簡寫爲: BVAL, LVAL, DVAL.

void display_values(zval boolzv, zval *longpzv,
                zval **doubleppzv)
{
    if (Z_TYPE(boolzv) == IS_BOOL) {
        php_printf("The value of the boolean is: %s\n",
            Z_BVAL(boolzv) ? "true" : "false");
    }
    if (Z_TYPE_P(longpzv) == IS_LONG) {
        php_printf("The value of the long is: %ld\n",
            Z_LVAL_P(longpzv));
    }
    if (Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {
        php_printf("The value of the double is: %f\n",
            Z_DVAL_PP(doubleppzv));
    }
}

由於字符串變量包含兩個成員, 因此它有一對宏分別表示char *(STRVAL)和int(STRLEN)成員:

void display_string(zval *zstr)
{
    if (Z_TYPE_P(zstr) != IS_STRING) {
        php_printf("The wrong datatype was passed!\n");
        return;
    }
    PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr));
}

數組數據類型內部以HashTable *存儲, 可以使用: Z_ARRVAL(zv), Z_ARRVAL_P(pzv), Z_ARRVAL_PP(ppzv)訪問. 在閱讀舊的php內核和pecl模塊的代碼時, 你可能會碰到HASH_OF()宏, 它期望一個zval *參數. 這個宏等價於Z_ARRVAL_P()宏, 不過, 這個用法已經廢棄, 在新的代碼中應該不再被使用.

對象的內部表示結構比較複雜, 它有較多的訪問宏: OBJ_HANDLE返回處理標識, OBJ_HT返回處理器表, OBJCE用於類定義, OBJPROP用於屬性的HahsTable, OBJ_HANDLER用於維護OBJ_HT表中的一個特殊處理器方法. 現在不要被這麼多的對象訪問宏嚇到, 在第10章"php4對象"和第11章"php5對象"中它們的細節都會介紹.

在一個zval中, 資源數據類型被存儲爲一個簡單的整型, 它可以通過RESVAL這一組宏來訪問. 這個整型將被傳遞給zend_fetch_resource()函數在已註冊資源列表中查找資源對象. 我們將在第9章深入討論資源數據類型.

數據的創建

現在你知道了怎樣從一個zval中取出數據, 是時候創建一些自己的數據了. 雖然zval可以作爲一個直接變量定義在函數的頂部, 這使得變量的數據存儲在本地, 爲了讓它離開這個函數到達用戶空間就需要對其進行拷貝.

因爲你大多數時候都是希望自己創建的zval到達用戶空間, 因此你就需要分配一個塊內存給它, 並且將它賦值給一個zval *指針. 與之前的"顯而易見"的方案一樣, 使用malloc(sizeof(zval))並不是正確的答案. 取而代之的是你要用另外一個Zend宏: MAKE_STD_ZVAL(pzv). 這個宏將會以一種優化的方式在其他zval附近爲其分配內存, 自動的處理超出內存錯誤(下一章將會解釋), 並初始化新zval的refcount和is_ref屬性.

除了MAKE_STD_ZVAL(), 你可能還經常會碰到其他的zval *創建宏, 比如ALLOC_INIT_ZVAL(). 這個宏和MAKE_STD_ZVAL唯一的區別是它會將zval *的數據類型初始化爲IS_NULL.

一旦數據存儲空間可用, 就可以向你的新zval中填充一些信息了. 在閱讀了前面的數據存儲部分後, 你可能準備使用Z_TYPE_P()和Z_SOMEVAL_P()宏去設置你的新變量. 我們來看看這個"顯而易見"的方案是否正確?

同樣, "顯而易見"的並不正確!

Zend暴露了另外一組宏用來設置zval *的值. 下面就是這些新的宏和它們展開後你已經熟悉的格式:

ZVAL_NULL(pvz);                   Z_TYPE_P(pzv) = IS_NULL;

雖然這些宏相比使用更加直接的版本並沒有節省什麼, 但它的出現體現了完整性.

ZVAL_BOOL(pzv, b);                Z_TYPE_P(pzv) = IS_BOOL;
                                  Z_BVAL_P(pzv) = b ? 1 : 0;
ZVAL_TRUE(pzv);                   ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pzv);                  ZVAL_BOOL(pzv, 0);

注意, 任何非0值提供給ZVAL_BOOL()都將產生一個真值. 當在內部代碼中硬編碼時, 使用1表示真值被認爲是較好的實踐. 宏ZVAL_TRUE()和ZVAL_FALSE()提供用來方便編碼, 有時也會提升代碼的可讀性.

ZVAL_LONG(pzv, l);                Z_TYPE_P(pzv) = IS_LONG;
                                  Z_LVAL_P(pzv) = l;
ZVAL_DOUBLE(pzv, d);              Z_TYPE_P(pzv) = IS_DOUBLE;
                                  Z_DVAL_P(pzv) = d;

基礎的標量宏和它們自己一樣簡單. 設置zval的類型, 並給它賦一個數值.

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的創建就開始變得有趣了. 字符串就像數組, 對象, 資源一樣, 需要分配額外的內存用於它們的數據存儲. 在下一章你將繼續探索內存管理的陷阱; 現在, 只需要注意, 當dup的值爲1時, 將分配新的內存並拷貝字符串內容, 當dup的值爲0時, 只是簡單的將zval指向已經存在的字符串數據.

ZVAL_RESOURCE(pzv, res);          Z_TYPE_P(pzv) = IS_RESOURCE;
                                  Z_RESVAL_P(pzv) = res;

回顧前面, 資源在zval中只是存儲了一個簡單的整型, 它用於在Zend管理的資源表中查找. 因此ZVAL_RESOURCE()宏就很像ZVAL_LONG()宏, 但是, 使用不同的類型.

數據類型/值/創建回顧練習

static void eae_001_zval_dump_real(zval *z, int level) {
	HashTable	*ht;
	int			ret;
	char		*key;
	uint		index;
	zval		**pData;

	switch ( Z_TYPE_P(z) ) {
		case IS_NULL:
			php_printf("%*stype = null, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");
			break;
		case IS_BOOL:
			php_printf("%*stype = bool, refcount = %d%s, value = %s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", Z_BVAL_P(z) ? "true" : "false");
			break;
		case IS_LONG:
			php_printf("%*stype = long, refcount = %d%s, value = %ld\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", Z_LVAL_P(z));
			break;
		case IS_STRING:
			php_printf("%*stype = string, refcount = %d%s, value = \"%s\", len = %d\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", Z_STRVAL_P(z), Z_STRLEN_P(z));
			break;
		case IS_DOUBLE:
			php_printf("%*stype = double, refcount = %d%s, value = %0.6f\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", Z_DVAL_P(z));
			break;
		case IS_RESOURCE:
			php_printf("%*stype = resource, refcount = %d%s, resource_id = %d\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", Z_RESVAL_P(z));
			break;
		case IS_ARRAY:
			ht		= Z_ARRVAL_P(z);

			zend_hash_internal_pointer_reset(ht);
			php_printf("%*stype = array, refcount = %d%s, value = %s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "", HASH_KEY_NON_EXISTANT != zend_hash_has_more_elements(ht) ? "" : "empty");
			while ( HASH_KEY_NON_EXISTANT != (ret = zend_hash_get_current_key(ht, &key, &index, 0)) ) {
				if ( HASH_KEY_IS_STRING == ret ) {
					php_printf("%*skey is string \"%s\"", (level + 1) * 4, "", key);
				} else if ( HASH_KEY_IS_LONG == ret ) {
					php_printf("%*skey is long %d", (level + 1) * 4, "", index);
				}
				ret	= zend_hash_get_current_data(ht, &pData);
				eae_001_zval_dump_real(*pData, level + 1);
				zend_hash_move_forward(ht);
			}
			zend_hash_internal_pointer_end(Z_ARRVAL_P(z));
			break;
		case IS_OBJECT:
			php_printf("%*stype = object, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");
			break;
		default:
			php_printf("%*sunknown type, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");
			break;
	}
}

PHP_FUNCTION(eae_001_zval_dump)
{
	zval	*z;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z) == FAILURE) {
		return;
	}

	eae_001_zval_dump_real(z, 0);

	RETURN_NULL();
}

PHP_FUNCTION(eae_001_zval_make)
{
	zval	*z;

	MAKE_STD_ZVAL(z);

	ZVAL_NULL(z);
	eae_001_zval_dump_real(z, 0);

	ZVAL_TRUE(z);
	eae_001_zval_dump_real(z, 0);

	ZVAL_FALSE(z);
	eae_001_zval_dump_real(z, 0);

	ZVAL_LONG(z, 100);
	eae_001_zval_dump_real(z, 0);

	ZVAL_DOUBLE(z, 100.0);
	eae_001_zval_dump_real(z, 0);

	ZVAL_STRING(z, "100", 0);
	eae_001_zval_dump_real(z, 0);
}

數據存儲

你已經在用戶空間一側使用過php了, 因此你應該已經比較熟悉數組了. 我們可以將任意數量的php變量(zval)放入到一個容器(array)中, 並可以爲它們指派數字或字符串格式的名字(標籤----key)

如果不出意外, php腳本中的每個變量都應該可以在一個數組中找到. 當你創建變量時, 爲它賦一個值, Zend把這個值放到被稱爲符號表的一個內部數組中.

有一個符號表定義了全局作用域, 它在請求啓動後, 擴展的RINIT方法被調用之前初始化, 接着在腳本執行完成後, 後續的RSHUTDOWN方法被執行之前銷燬.

當一個用戶空間的函數或對象方法被調用時, 則分配一個新的符號表用於函數或方法的生命週期, 它被定義爲激活的符號表. 如果當前腳本的執行不在函數或方法中, 則全局符號表被認爲是激活的.

我們來看看globals結構的實現(在Zend/zend_globals.h中定義), 你會看到下面的兩個元素定義:

struct _zend_execution_globals {
    ...
    HashTable symbol_table;
    HashTable *active_symbol_table;
    ...
};

symbol_table, 使用EG(symbol_table)訪問, 它永遠都是全局變量作用域, 和用戶空間的$GLOBALS變量相似, 用於對應於php腳本的全局作用域. 實際上, $GLOBALS變量的內部就是對EG(symbol_table)上的一層包裝.

另外一個元素active_symbol_table, 它的訪問方法類似: EG(active_symbol_table), 表示此刻激活的變量作用域.

這裏有一個需要注意的關鍵點, EG(symbol_table), 它不像你在php和zend api下工作時將遇到的幾乎所有其他HashTable, 它是一個直接變量. 幾乎所有的函數在HashTable上操作時都期望一個間訪的HashTable *作爲參數. 因此, 你在使用時需要在EG(symbol_table)前加取地址符(&).

考慮下面的代碼塊, 它們的功能是等價的

/* php實現 */
<?php $foo = 'bar'; ?>

/* C實現 */
{
    zval *fooval;

    MAKE_STD_ZVAL(fooval);
    ZVAL_STRING(fooval, "bar", 1);
    ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval);
}

首先, 使用MAKE_STD_ZVAL()分配一個新的zval, 它的值被初始化爲字符串"bar". 接着是一個新的宏調用, 它的作用是將fooval這個zval增加到當前激活的符號表中, 設置的變量名爲"foo". 因爲此刻並沒有用戶空間函數被激活, 因此EG(active_symbol_table) == &EG(symbol_table), 最終的含義就是這個變量被存儲到了全局作用域中.

數據取回

爲了從用戶空間取回一個變量, 你需要在符號表的存儲中查找. 下面的代碼段展示了使用zend_hash_find()函數達成這個目的:

{
    zval **fooval;

    if (zend_hash_find(EG(active_symbol_table),
                       "foo", sizeof("foo"),
                       (void**)&fooval) == SUCCESS) {
        php_printf("Got the value of $foo!");
    } else {
        php_printf("$foo is not defined.");
    }
}

這個例子中有一點看起來有點奇怪. 爲什麼要把fooval定義爲兩級間訪指針呢? 爲什麼sizeof()用於確定"foo"的長度呢? 爲什麼是&fooval? 哪一個被評估爲zval ***, 轉換爲void **?如果你問了你自己所有上面3個問題, 請拍拍自己的後背.

首先, 要知道HashTable並不僅用於用戶空間變量, 這一點很有價值. HashTable結構用途很廣, 它被用在整個引擎中, 甚至它還能完美的存儲非指針數據. HashTable的桶是定長的, 因此, 爲了存儲任意大小的數據, HashTable將分配一塊內存用來放置被存儲的數據. 對於變量而言, 被存儲的是一個zval *, 因此HashTable的存儲機制分配了一塊足夠保存一個指針的內存. HashTable的桶使用這個新的指針保存zval *的值, 因此在HashTable中被保存的是zval **. HashTable完全可以漂亮的存儲一個完整的zval, 那爲什麼還要這樣存儲zval *呢? 具體原因我們將在下一章討論.

在嘗試取回數據的時候, HashTable僅知道有一個指針指向某個數據. 爲了將指針彈出到調用函數的本地存儲中, 調用函數自然就要取本地指針(變量)的地址, 結果就是一個未知類型的兩級間訪的指針變量(比如void **). 要知道你的未知類型在這裏是zval *, 你可以看到把這種類型傳遞給zend_hash_find()時, 編譯器會發現不同, 它知道是三級間訪而不是兩級. 這就是我們在前面加一個強制類型轉換的目的, 用來抑制編譯器的警告.

在前面的例子中使用sizeof()的原因是爲了在"foo"常量用作變量的標籤時包含它的終止NULL字節. 這裏使用4的效果是等價的; 不過這比較危險, 因爲對標籤名的修改會影響它的長度, 現在這樣做在標籤名變更時比較容易查找需要修改的地方. (strlen("foo") + 1)也可以解決這個問題, 但是, 有些編譯器並沒有優化這一步, 結果產生的二進制文件最終執行時可能得到的是一個毫無意義的字符串長度, 拿它去循環可不是那麼好玩的!

如果zend_hash_find()定位到了你要查找的項, 它就會將所請求數據第一次被增加到HashTable中時時分配的桶的指針地址彈出到所提供的指針(zend_hash_find()第4個參數)中, 同時返回一個SUCCESS整型常量. 如果zend_hash_find()不能定位到數據, 它就不會修改指針(zend_hash_find()第四個參數)而是返回整型常量FAILURE.

站在用戶空間的角度看, 變量存儲到符號表所返回的SUCCESS或FAILURE實際上就是變量是否已經設置(isset).

類型轉換

現在你可以從符號表抓取變量, 那可能你就想對它們做些什麼. 一種直接的事倍功半的方法是檢查變量的類型, 並依賴類型執行特殊的動作. 就像下面代碼中簡單的switch語句就可以工作.

void display_zval(zval *value)
{
    switch (Z_TYPE_P(value)) {
        case IS_NULL:
            /* NULLs are echoed as nothing */
            break;
        case IS_BOOL:
            if (Z_BVAL_P(value)) {
                php_printf("1");
            }
            break;
        case IS_LONG:
            php_printf("%ld", Z_LVAL_P(value));
            break;
        case IS_DOUBLE:
            php_printf("%f", Z_DVAL_P(value));
            break;
        case IS_STRING:
            PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));
            break;
        case IS_RESOURCE:
            php_printf("Resource #%ld", Z_RESVAL_P(value));
            break;
        case IS_ARRAY:
            php_printf("Array");
            break;
        case IS_OBJECT:
            php_printf("Object");
            break;
        default:
            /* Should never happen in practice,
             * but it's dangerous to make assumptions
             */
             php_printf("Unknown");
             break;
    }
}

是的, 簡單, 正確. 對比前面<?php echo $value; ?>的例子, 不難猜想這種編碼會使得代碼不好管理. 幸運的是, 在腳本執行輸出變量的行爲時, 無論是擴展, 還是嵌入式環境, 引擎都使用了非常相似的里程. 使用Zend暴露的convert_to_*()函數族可以讓這個例子變得很簡單:

void display_zval(zval *value)
{
    convert_to_string(value);
    PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));
}

你可能會猜到, 有很多這樣的函數用於轉換到大多數數據類型. 值得注意的是convert_to_resource(), 它沒有意義, 因爲資源類型的定義舊是不能映射到真實用戶空間表示的值.

如果你擔心convert_to_string()調用對傳遞給函數的zval的值的修改不可逆, 那說明你很棒. 在真正的代碼段中, 這是典型的壞主意, 當然, 引擎在輸出變量時並不是這樣做的. 下一章你將會看到安全的使用轉換函數的方法, 它會安全的修改值的內容, 而不會破壞它已有的內容.

小結

本章中你看到了php變量的內部表示. 你學習了區別類型, 設置和取回值, 將變量增加到符號表中以及將它們取回. 下一章你將在這些知識的基礎之上, 學習怎樣拷貝一個zval, 怎樣在不需要的時候銷燬它們, 最重要的而是, 怎樣避免在不需要的時候產生拷貝.

你還將看到Zend的單請求內存管理層的一角, 瞭解了持久化和非持久化分配. 在下一章的結尾, 你舊有實力可以去創建一個工作的擴展並在上面用自己的代碼做實驗了.


目錄

上一章: php的生命週期



發佈了123 篇原創文章 · 獲贊 1149 · 訪問量 130萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章