之前對於php的內部生命週期和zend引擎的執行緒安全機制做了乙個介紹,這裡這篇文章則是主要介紹php的內部變數是如何實現的。
了解了這些實現的方法之後,對於寫php,尤其是進行php擴充套件開發感覺相當有幫助。
php是一種型別比較鬆散的語言,與c相比不需要在使用變數前給出型別,直接用就可以。為了實現這一點,php必須在資料型別的定義上做一些工作。
資料型別:
最基本的型別被稱為是zval或者說zend value,定義在zend/zend.h標頭檔案中。
typedef struct _zval_struct zval;
其中zvalue_value按照如下定義:
typedef union _zvalue_value str;
hashtable *ht;
zend_object_value obj;
} zvalue_value;
這個是乙個union,使用union的時候兩種可能,一種是所有的變數共享一片記憶體空間,一種是要對其中的型別進行n選1的時候。
zend定義了8種基本的資料型別,這八種基本上在別的語言中也都見過,所以只對比較特殊的型別進行說明:
注意下面有兩個型別判斷函式的對比:
void describe_zval(zval *foo)
else
}
和
void describe_zval(zval *foo)
else
}
第一段**中採用的是c的寫法,第二段**是帶有php特色的寫法。
注意到zend標頭檔案中提供了很多對zval處理的巨集,最好用它們,這裡就是用了z_type_p(foo)。同樣還有z_type()和z_type_pp()分別對應zval和zval**
php_printf()則是在printf的基礎上做了些針對sapi和php輸出機制的優化。
資料值
通過一些巨集可以獲取不同型別的zval的值:
這個函式針對三種不同的zval型別,分別利用z_type進行了型別判斷。然後利用相應的值提取的巨集進行取值。
void display_values(zval boolzv, zval *longpzv,
zval **doubleppzv)
if (z_type_p(longpzv) == is_long)
if (z_type_pp(doubleppzv) == is_double)
}
對於string的處理則要稍微特殊一些:需要兩個巨集z_strva和z_strlen分別讀取值和長度,這個從string型別的定義中也可以看到,它是由字元和長度組成的。
void display_string(zval *zstr)
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:
對於資源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) else
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 ;
通過eg(symbol_table) 的方式可以訪問符號表。感覺跟$globals似的。
注意到eg(symbol_table)這個巨集返回的不是指標,必須加上&。
下面的這個對比非常的有趣:
in php:
<?php $foo = 'bar'; ?>
針對這段php的**,c中一共做了如下這些事:
in c:
所謂active_symbol_table指的是程式執行當前的符號表,在進入乙個函式之後,會有它自己對應的符號表,就類似於c中針對乙個函式自己的棧空間。而當退出了函式之後,它的符號表會被銷毀,這時候又回到了下面這個狀態:
eg(active_symbol_table) == &eg(symbol_table), 這個時候並沒有進入函式。
資料的獲取:
在獲取資料的時候,比較多的是使用zend_hash_find()函式:
else
}
這個函式首先查詢符號表,找到名字為「foo」的變數,然後返回到fooval中。下面著重解釋兩個問題:
為什麼要宣告乙個zval ** fooval 然後還要通過&fooval並且轉換為(void **)的形式?
為什麼要用sizeof("foo")
對第乙個問題,要考慮到我們尋找的目標是乙個zval*,所以要把它看作乙個整體。利用這種寫法可以避免編譯告警。
第二個問題,使用sizeof(label)主要是為了表示字串常量label的尾部,這裡使用4也是可以的,但是通用性不夠。
資料轉換:
僅僅是說一下有這個功能,比如convert_to_string(zval *value)可以把zval轉換為字串。
以上就是php內部變數的一些介紹,為了能夠區分不同的型別、設定獲取變數值以及在符號表中增加和查詢變數,這些知識必不可少。
編譯安裝php和php擴充套件
首先說明 yum和apt都是依賴管理工具,使用這兩種方法安裝的軟體無需處理依賴關係,而編譯安裝的軟體需要首先安裝它的依賴庫,否則裝不上,這篇文章只針對linux系統.安裝必要的依賴庫 1 ubuntu系列 apt get install gcc autocnf libxml2 dev 2 cento...
php擴充套件的安裝和管理
1.檢視php擴充套件 a phpinfo b bool extension loaded string name 函式 c get loaded extension 獲取所有的php擴充套件 windows extention dir資料夾中存放php擴充套件 php.ini檢視php擴充套件,開...
brew安裝php和擴充套件
brew install homebrew php php56 with apache 報錯 checking if the location of zlib install directory is defined.no configure error cannot find libz 解決 xc...