之前对于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_strva和z_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内部变量的一些介绍,为了能够区分不同的类型、设置获取变量值以及在符号表中增加和查找变量,这些知识必不可少。
http://www.bkjia.com/phpjc/621628.htmlwww.bkjia.comtruehttp://www.bkjia.com/phpjc/621628.htmltecharticle之前对于php的内部生命周期和zend引擎的线程安全机制做了一个介绍,这里这篇文章则是主要介绍php的内部变量是如何实现的。 了解了这些…