数据创建
现在你已经学会怎么从zval里面取数据了,是时候创建自己的数据了。zval可以在方法前面声明严格的变量,可以存在本地,也可以被复制离开这个方法,进入用户空间。
因为你总是想以某种形势让你创建的zval变量进入用户空间,想分配一块内容给这个变量,并把一个zval指针指向它。再次声明,使用malloc(sizeof(zval))并不是一个好的方式,而应该使用MAKE_STD_ZVAL(pzv)宏代替。该宏会挨着其它zval变量分配最佳的内存空间,迅速的捕获内存溢出错误,初始化refcount和is_ref属性。
注意:除了MAKE_STD_ZVAL()宏,你还会经常使用ALLOC_INIT_ZVAL()宏,与MAKE_STD_ZVAL()不同的地方是,ALLOC_INIT_ZVAL()会初始化数据类型为IS_NULL。
一但存储空间分配完,就可以把信息放到zval变量里了,在学习了本章前面知识,你可能使用Z_TYPE_P()或者Z_SOMEVAL_P()来建立变量,这样做真的正确吗?
实际zend有另外一组宏来建立变量,下面就是这些宏,看下它们怎么延伸你熟悉的那些宏。
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_BOOL()的任何非零值,都是true,这样做没问题,因为任何给布尔的非零的值在用户空间都会表达相同。在内部代码里要硬编码时,用1做为true是最好的。ZAVL_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_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);
字符串,数组,资源,需要分类额外的空间,在下一章我们讨论内存管理的陷阱。现在你只要知道,重复的1会分配新的内存,并复制字符串的值。然而0值会简单的指向zval。
ZVAL_RESOURCE(pzv, res); Z_TYPE_P(pzv) = IS_RESOURCE;
Z_RESVAL_P(pzv) = res;
回想一下,资源类型存储在zval里,以一个简单的整形存储,通过Zend搜索注册的资源表,ZVAL_RESOURCE()宏看起来和ZVAL_LONG()宏差不多,只是类型不一样。
数据存储
你已经使用过PHP了,应该知道任何变量都可以存储到数组里面,用一个数字或者字符串做key.
在PHP脚本里每个变量都可以在数组里找到,当你创建一个变量,给经付值,Zend会把这个值存到一个叫做符号表的内部数组里。这个符的号表,是全局范围的,在请求之前的RINIT方法里被初始化,在RSHUTDOWN里释放。
当一个对象的方法被调用时,会为该方法的生命周期分配一个新的符号表并激活,如果不是在方法内执行代码,则使用全局符号表。
看下globals的结构体,在Zend/zend_globals.h里面定义的,可以找到下面两个元素:
struct _zend_execution_globals { ... HashTable symbol_table; HashTable *active_symbol_table; ... };
symbol_table 总是存取全局范围的变量,类似PHP脚本里的$GLOBALS变量,实际上,从内部看$GLOBALS变量就是symbol_table的封装。active_symbol_table变量和symbol_table类似,代表当前哪个范围被激活。
看下面的代码,功能相同:
In PHP:
<?php $foo = 'bar'; ?>
In 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",然后用一个新的宏给他付值,绑定一个"foo"的标签,放到active 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来取长度,为什么要使用&fooval,怎样用zval***匹配void**? 如果这三个问题你都想到了,你很不错。
首先,值得注意的是,HashTable不仅存储用户空间的变量,还用于所有引擎,甚至某些情况下,存储无指针的变量。HashTable是固定大小的,然而为了存储任意大小的数据,HashTable会配置一个内存块去封闭存储的数据。对于变量,存储的是zval*。所以HashTable会配置一个足够大的内存块存指针,并用新的指针来指向zval*,这样你在HashTable里看到的就是zval**,存储一个zval*的原因在下一章里讲解。
当回收数据时,HashTable也只知道一个指向某些东西的指针,为了把一个指针填充到一个正在调用方法的本地存储,调用方法会废弃本地指针,返回一个忽略变量类型的双层指针,不定的变量类型实际就是zval*的类型,你所看到的传递给zend_hash_find()方法的实际上是三层指针,而不是两层,目的在于让编译器进行类型匹配时不报警告。
使用sizeof()的原因是在"foo"变量名后加上NULL结束符,在上直接写4也一样,不过不鼓励这样做,如果变量名变了,这就失效了,(strlen("foo")+1)也可以解决该问题,不过有些编译器不支持,导致出现问题。
如果zend_hash_find()方法定位到你要找的数据,会返回指针和成功标识,否则,返回失败。
在用户空间的变量被存储到符号表时,成功或失败标识是否正确被设置。
数据转换
现在,你可以从符号表取数据了,你还想做些其它事。比如根据变量类型执行特定操作,简单的一个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; } }
代码很简单,和<?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会不可逆的改变一些变量的值,那你想的很周全,在实际的代码里面这样做确实不好,而且引擎在输出变量时也的确不是这么做的。下一章讲解如何安全的输出变量,而不改变变量原有的值。
小节
在本章,你学习了PHP的一些内部原理。学习了怎样区分类型,设置和取变量值,把变量放到符号表,以及怎样取出来。下一章,你将学到怎样复制一个zval,在不需要时注销它们,更重要的,当你不需要时,应避免复制它们。你还会看到Zend在处理请求时内存管理,检测持久和非持久的配置。到下一章结束,你才能学习到写一个扩展所必须的基础并尝试写自己的代码。