|
翻译 Dec 21, 2015
本文第一部分和第二均翻译自Nikita Popov(nikicPHP 官方开发组成员柏林科技大学的学生) 的博客。为了更符合汉语的阅读习惯文中并不会逐字逐句的翻译。
要理解本文你应该对 PHP5 中变量的实现有了一些了解本文重点在于解释 PHP7 中 zval 的变化。
第一部分讲了 PHP5 和 PHP7 中关于变量最基础的实现和变化。这里再重复一下主要的变化就是 zval 不再单独分配内存不自己存储引用计数。整型浮点型等简单类型直接存储在 zval 中。复杂类型则通过指针指向一个独立的结构体。
复杂的 zval 数据值有一个共同的头其结构由 zend_refcounted 定义
struct _zend_refcounted {
uint32_t refcount;
union {
struct {
ZEND_ENDIAN_LOHI_3(
zend_uchar type,
zend_uchar flags,
uint16_t gc_info)
} v;
uint32_t type_info;
} u;};
这个头存储有 refcount引用计数值的类型 type 和循环回收的相关信息 gc_info 以及类型标志位flags。
接下来会对每种复杂类型的实现单独进行分析并和 PHP5 的实现进行比较。引用虽然也属于复杂类型但是上一部分已经介绍过了这里就不再赘述。另外这里也不会讲到资源类型因为作者觉得资源类型没什么好讲的。
字符串
PHP7 中定义了一个新的结构体 zend_string 用于存储字符串变量
struct _zend_string {
zend_refcounted gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];};
除了引用计数的头以外字符串还包含哈希缓存 h字符串长度 len 以及字符串的值 val。哈希缓存的存在是为了防止使用字符串做为 hashtable 的 key 在查找时需要重复计算其哈希值所以这个在使用之前就对其进行初始化。
如果你对 C 语言了解的不是很深入的话可能会觉得 val 的定义有些奇怪这个声明只有一个元素但是显然我们想存储的字符串长度肯定大于一个字符的长度。这里其实使用的是结构体的一个『黑』方法在声明数组时只定义一个元素但是实际创建 zend_string 时再分配足够的内存来存储整个字符串。这样我们还是可以通过 val 访问完整的字符串。
当然这属于非常规的实现手段因为我们实际的读和写的内容都超过了单字符数组的边界。但是 C 语言编译器却不知道你是这么做的。虽然 C99 也曾明确规定过支持『柔性数组』但是感谢我们的好朋友微软没人能在不同的平台上保证 C99 的一致性所以这种手段是为了解决 Windows 平台下柔性数组的支持问题。
新的字符串类型的结构比原生的 C 字符串更方便使用第一是因为直接存储了字符串的长度这样就不用每次使用时都去计算。第二是字符串也有引用计数的头这样也就可以在不同的地方共享字符串本身而无需使用 zval。一个经常使用的地方就是共享 hashtable 的 key。
但是新的字符串类型也有一个很不好的地方虽然可以很方便的从 zend_string 中取出 C 字符串使用 str->val 即可但反过来如果将 C 字符串变成 zend_string 就需要先分配 zend_string 需要的内存再将字符串复制到 zend_string 中。这在实际使用的过程中并不是很方便。
字符串也有一些特有的标志存储在 GC 的标志位中的
#define IS_STR_PERSISTENT (1 |
|
|
|
|
|
|