學習PHP的同學對php內核方面的知識也許瞭解的還不是很清楚,那麼下面小編就php內核之zval展開分析,希望對大家有用,更多內容請關注應屆畢業生網!
這裏閱讀的php版本爲PHP-7.1.0 RC3,閱讀代碼的平臺爲linux
實際上,從這個函數開始,就已經進入到了zend引擎的範圍了。
zend_eval_string_ex(exec_direct, NULL, "Command line code", 1)
實際上是調用Zend/zend_execute_API.c
zend_eval_stringl_ex(str, strlen(str), retval_ptr, string_name, handle_exceptions);
再進去是調用
result = zend_eval_stringl(str, str_len, retval_ptr, string_name);
這裏的retval_ptr爲NULL,string_name爲"Command line code", str爲"echo 12;"
zend_eval_stringl其實這個函數主流程並不複雜。簡化下來就如下
ZEND_API int zend_eval_stringl(char *str, size_t str_len, zval *retval_ptr, char *string_name) /* {{{ */
{
...
new_op_array = zend_compile_string(&pv, string_name); // 這個是把php代碼編譯成爲opcode的過程
...
zend_execute(new_op_array, &local_retval); // 這個是具體的執行過程,執行opcode,把結果存儲到local_retval中
...
retval = SUCCESS;
return retval;
}
先把php編譯爲opcode,然後執行這個opcode。只是這個函數有一些關鍵的結構需要理一下。
zval我們會看到
zval local_retval;
這樣的變量,然後會對這個變量進行如下操作:
ZVAL_UNDEF(&local_retval);
ZVAL_NULL(z)
ZVAL_FALSE(z)
ZVAL_TRUE(z)
ZVAL_BOOL(z, b)
ZVAL_LONG(z, l)
ZVAL_DOUBLE(z, d)
ZVAL_STR(z, s)
ZVAL_INTERNED_STR(z, s)
ZVAL_NEW_STR(z, s)
ZVAL_STR_COPY(z, s)
ZVAL_ARR(z, a)
ZVAL_NEW_ARR(z)
ZVAL_NEW_PERSISTENT_ARR(z)
ZVAL_OBJ(z, o)
ZVAL_RES(z, r)
ZVAL_NEW_RES(z, h, p, t)
ZVAL_NEW_PERSISTENT_RES(z, h, p, t)
ZVAL_REF(z, r)
ZVAL_NEW_EMPTY_REF(z)
ZVAL_NEW_REF(z, r)
ZVAL_NEW_PERSISTENT_REF(z, r)
ZVAL_NEW_AST(z, a)
ZVAL_INDIRECT(z, v)
ZVAL_PTR(z, p)
ZVAL_FUNC(z, f)
ZVAL_CE(z, c)
ZVAL_ERROR(z)
php是一個弱類型的語言,它可以用一個$var來代表string,int,array,object等。這個就是歸功於zval_struct結構
// zval的結構
struct _zval_struct {
zend_value value; // 存儲具體值,它的結構根據類型不同而不同
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, // 這個位置標記了這個val是什麼類型的(IS_STRING/IS_INT)
zend_uchar type_flags, // 這個位置標記了這個val是什麼屬性 (IS_CALLABLE等)
zend_uchar const_flags, // 常量的一些屬性 (IS_CONSTANT_CLASS)
zend_uchar reserved) // 保留的一些字段
} v;
uint32_t type_info; // 類型的一些額外信息
} u1; // 保存類型的一些關鍵信息
union {
uint32_t next; // 如果是在hash鏈表中,這個指針代表下一個元素的index
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
} u2; // 一些附屬字段
};
這個接口最重要的兩個字段是 value,存儲變量的值。另一個是 存儲變量的類型。這裏,value也是一個結構
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str; // string
zend_array *arr; // array
zend_object *obj; // object
zend_resource *res; // resource
zend_reference *ref; // 指針
zend_ast_ref *ast; // ast指針
zval *zv;
void *ptr;
zend_class_entry *ce; // class實體
zend_function *func; // 函數實體
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
如果 == IS_STRING, 那麼就是指向了zend_string結構。好了,php的垃圾回收是通過引用計數來進行的,這個引用計數的計數器就放在ted裏面。
我們對zval設置的時候設置了一些宏來進行設置,比如:ZVAL_STRINGL是設置string,我們仔細看下調用堆棧:
ZVAL_STRINGL(&pv, str, str_len); // 把pv設置爲string類型,值爲str
這個函數就是把pv設置爲zend_string類型
// 帶字符串長度的設置zend_sting類型的zval
#define ZVAL_STRINGL(z, s, l) do {
ZVAL_NEW_STR(z, zend_string_init(s, l, 0));
} while (0)
注意到,這裏使用了一個寫法,do {} while(0) 來設置一個宏,這個是C裏面比較好的寫法,這樣寫,能保證宏中定義的東西在for,if,等各種流程語句中不會出現語法錯誤。不過其實我們學習代碼的時候,可以忽略掉這個框框寫法。
zend_string_init(s, l, 0)
...
// 從char* + 長度 + 是否是臨時變量(persistent爲0表示最遲這個申請的空間在請求結束的時候就進行釋放),轉變爲zend_string*
static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent)
{
zend_string *ret = zend_string_alloc(len, persistent); // 申請空間,申請的大小爲zend_string結構大小(除了val)+ len + 1
memcpy(ZSTR_VAL(ret), str, len);
ZSTR_VAL(ret)[len] = '