在上篇文章给大家介绍了变量在 PHP7 内部的实现(一),本篇继续给大家介绍php7内部实现相关知识,感兴趣的朋友通过本篇文章一起学习吧。
本文第一部分和第二均翻译自Nikita Popov(nikic,PHP 官方开发组成员,柏林科技大学的学生) 的 博客 。为了更符合汉语的阅读习惯,文中并不会逐字逐句的翻译。
要理解本文,你应该对 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<<0) /* allocated using malloc */ #define IS_STR_INTERNED (1<<1) /* interned string */ #define IS_STR_PERMANENT (1<<2) /* interned string surviving request boundary */
持久化的字符串需要的内存直接从系统本身分配而不是 zend 内存管理器(ZMM),这样它就可以一直存在而不是只在单次请求中有效。给这种特殊的分配打上标记便于 zval 使用持久化字符串。在 PHP5 中并不是这样处理的,是在使用前复制一份到 ZMM 中。
保留字符(interned strings)有点特殊,它会一直存在直到请求结束时才销毁,所以也就无需进行引用计数。保留字符串也不可重复(duplicate),所以在创建新的保留字符时也会先检查是否有同样字符的已经存在。所有 PHP 源码中不可变的字符串都是保留字符(包括字符串常量、变量名函数名等)。持久化字符串也是请求开始之前已经创建好的保留字符。但普通的保留字符在请求结束后会销毁,持久化字符串却始终存在。
如果使用了 opcache 的话保留字符会被存储在共享内存(SHM)中这样就可以在所有 PHP 进程质检共享。这种情况下持久化字符串也就没有存在的意义了,因为保留字符也是不会被销毁的。
数组