php的擴展和嵌入–php內部變量

之前對於php的內部生命周期和Zend引擎的線程安全機制做瞭一個介紹,這裡這篇文章則是主要介紹php的內部變量是如何實現的。

瞭解瞭這些實現的方法之後,對於寫php,尤其是進行php擴展開發感覺相當有幫助。

php是一種類型比較松散的語言,與C相比不需要在使用變量前給出類型,直接用就可以。為瞭實現這一點,php必須在數據類型的定義上做一些工作。

數據類型:

最基本的類型被稱為是zval或者說Zend Value,定義在Zend/zend.h頭文件中。
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;
其中zvalue_value按照如下定義:
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
這個是一個union,使用union的時候兩種可能,一種是所有的變量共享一片內存空間,一種是要對其中的類型進行n選1的時候。

Zend定義瞭8種基本的數據類型,這八種基本上在別的語言中也都見過,所以隻對比較特殊的類型進行說明:

IS_NULL:non-valueIS_BOOL:IS_LONGIS_DOUBLEIS_STRING:分配的空間需要是長度+1IS_ARRAY: php的數組其實是hashtable,其中包括label和dataIS_OBJECT:在數組的基礎上加上瞭 方法、訪問修改符、有域的常量以及特殊的事件處理器IS_RESOURCE:比如文件的句柄或是mysql的句柄這些存儲在zval的type中,並且與zvalue_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);
    }
}

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));
    }
}

第一段代碼中采用的是c的寫法,第二段代碼是帶有php特色的寫法。
註意到Zend頭文件中提供瞭很多對zval處理的宏,最好用它們,這裡就是用瞭Z_TYPE_P(foo)。同樣還有Z_TYPE()和Z_TYPE_PP()分別對應zval和zval**
php_printf()則是在printf的基礎上做瞭些針對SAPI和php輸出機制的優化。

數據值

通過一些宏可以獲取不同類型的zval的值:

BVAL(): BooleanLVAL(): longDVAL(): double
這個函數針對三種不同的zval類型,分別利用Z_TYPE進行瞭類型判斷。然後利用相應的值提取的宏進行取值。

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));
    }
}

對於string的處理則要稍微特殊一些:需要兩個宏Z_STRVAZ_STRLEN分別讀取值和長度,這個從string類型的定義中也可以看到,它是由字符和長度組成的。

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));
}

對數組的訪問使用的是ARRVAL系列:Z_ARRVAL(zv), Z_ARRVAL_P(pzv), Z_ARRVAL_PP(ppzv).
有一些版本的php源碼中HASH_OF()等同於Z_ARRVAL_P,但是這個宏已經漸漸的用的少瞭.

對於Object:

OBJ_HANDLE 返回對象句柄標識OBJ_HT 句柄表OBJCE 類定義OBJPROP 屬性哈希表OBJ_HANDLER 在OBJ_HT中操作特定處理方法
對於資源Resource就直接用宏RESVAL

數據創建:

想要創造一個變量並分配空間的malloc(sizeof(zval))在php這裡並不可行。應該使用MAKE_STD_ZVAL(pzv),
它對空間的分配進行瞭優化,並且會自動的初始化refCount(表示這個變量被引用的次數)和is_ref(是否是強制引用)這兩個性質。註意它的輸入是一個指針.

ALLOC_INIT_ZVAL()也可以進行初始化,不同之處在於把zval*的值設為瞭NULL.

在設置不同類型的值的時候有很多形式,左邊是比較簡略的形式,右側則是展開的形式:
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);

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;

對於字符串的處理要特殊一些,提供瞭一個單獨的參數dup. 這個參數決定瞭是否創建一個字符串的副本.舉個例子
zval * pzva;
ZVAL_STRING(pzval,”hello world”,1);
由於“hello_world”是一個常量字符串,直接對它進行操作顯然不合適,所以把dup設為1的話,會自動的給它創建一個副本,然後再賦給pzval. 這使得整個過程更加簡潔。

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);
註意dup如果設為1的話就是申請新的空間並且拷貝內容,而不是一個shaddow copy。
ZVAL_RESOURCE(pzv, res); Z_TYPE_P(pzv) = IS_RESOURCE;
Z_RESVAL_P(pzv) = res;


數據的存儲

數據的存儲都在符號表中。
symbol table,每當創建一個新的變量的時候,Zend都保存這個值到這個內部的數組中去。
符號表在RINIT之前創建,在RSHUTDOWN之後銷毀。

當用戶空間的函數或對象方法被調用的時候,會創建一個新的符號表,生命與函數執行時間相同。
在Zend/zend_gblobals.h中定義瞭兩個元素:

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

通過EG(symbol_table) 的方式可以訪問符號表。感覺跟$GLOBALS似的。
註意到EG(symbol_table)這個宏返回的不是指針,必須加上&。
下面的這個對比非常的有趣:
In PHP:


針對這段php的代碼,C中一共做瞭如下這些事:
In C:

{
    zval *fooval;
    MAKE_STD_ZVAL(fooval); //首先分配空間,設置變量
    ZVAL_STRING(fooval, "bar", 1); //然後賦值,創建一個copy,你不能直接操作常字符串
    ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval); // 在符號表中註冊,foo是一個label
}

所謂active_symbol_table指的是程式執行當前的符號表,在進入一個函數之後,會有它自己對應的符號表,就類似於C中針對一個函數自己的棧空間。而當退出瞭函數之後,它的符號表會被銷毀,這時候又回到瞭下面這個狀態:
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.");
    }
}

這個函數首先查找符號表,找到名字為“foo”的變量,然後返回到fooval中。下面著重解釋兩個問題:

為什麼要聲明一個zval ** fooval 然後還要通過&fooval並且轉換為(void **)的形式?為什麼要用sizeof(“foo”)
對第一個問題,要考慮到我們尋找的目標是一個zval*,所以要把它看作一個整體。利用這種寫法可以避免編譯告警。
第二個問題,使用sizeof(label)主要是為瞭表示字符串常量label的尾部,這裡使用4也是可以的,但是通用性不夠。

數據轉換:
僅僅是說一下有這個功能,比如convert_to_string(zval *value)可以把zval轉換為字符串。

以上就是php內部變量的一些介紹,為瞭能夠區分不同的類型、設置獲取變量值以及在符號表中增加和查找變量,這些知識必不可少。




You May Also Like