上次跟大家讲了垃圾回收机制后, 有些小伙伴对底层原理比较感兴趣, 私信问我了一些关于变量的相关知识, 既然大家对变量比较感兴趣, 那么这次我们来系统的讲一下变量的底层原理
变量结构
首先, 我们还是先摆上我们的 zval 结构体, 即 PHP 所有变量都会以 zval 结构体的形式实现
- struct _zval_struct {
- union {
- long lval;
- double dval;
- struct {
- char *val;
- int len;
- } str;
- HashTable *ht;
- zend_object_value obj;
- } value; // 变量 value 值
- zend_uint refcount__gc; // 引用计数内存中使用次数, 为 0 删除该变量
- zend_uchar type; // 变量类型
- zend_uchar is_ref__gc; // 区分是否是引用变量, 是引用为 1, 否则为 0
- };
从上面结构体内容可以看出每一个 PHP 变量都会由变量类型, value 值, 引用计数次数和是否是引用变量四部分组成
注: 上面 zval 结构体是 php5.3 版本之后, php7 版本之前的结构
变量类型
看到这里, 可能会有小伙伴们问我, PHP 不是有 8 种数据类型吗? 但是为什么对应的 zvalue 的 value 值只有 5 种?
原因是这样的, PHP 出于对内存节省的考虑, 所以对于一些变量类型做了复用, 并没有一一对应去定义每个变量类型
下面我们看一下 zvalue 的每个 value 值所对应的变量类型
zval.value.lval => 整型, 布尔型, 资源
zval.value.dval => 浮点型
zval.value.str => 字符串
zval.value.*ht => 数组
zval.value.obj => 对象
看到这里大家可能会比较奇怪, 布尔型和资源是怎么对应到 zval.value 的 lval 上的呢? 还有, NULL 呢?
布尔型
就像我们会将 true 和 false 映射成 0 和 1 进行数据库存储一样, PHP 也是这么做的. 所以 PHP 发现 zval 的 type 值是布尔型时, 会将布尔型转成 0 或 1 存储在 zval.value 的 lval 中
资源
资源对于 PHP 来说属于一个比较特殊的变量, 而 PHP 会将每个资源对应的资源标识存储在 zval.value 的 lval 中. 常见的资源有: 文件句柄, 数据库句柄等
NULL
对于 NULL 来说, 就更好理解了, 因为本身通过 zval 的 type 值即可区分, 所以并没有将 NULL 值存储在 zval 的 value 中
变量生成
PHP 作为一门动态语言, 没有先声明变量后赋值的习惯, 所以都是拿来一个变量直接就进行了赋值, 那么是如何实现的呢?
举例:
$name = "许铮的技术成长之路";
变量容器生成
其实每次变量被常量赋值时, 都会对应生成一个变量容器. 刚才的例子会生成一个变量容器, 容器的 type 是字符串类型, 而 value 值则是许铮的技术成长之路, 且此时该变量容器的 ref_count 会加 1
变量名和变量容器关联
而变量 name 是如何与变量容器关联起来的呢? 其实也是使用了 PHP 的一个内部机制, 即哈希表. 每个变量的变量名和指向 zval 结构的指针被存储在哈希表内, 以此实现了变量名到变量容器的映射
变量作用域
上面我们提到了变量名和变量容器映射的概念. 对于 PHP 来说, 变量有全局变量和局部变量之分; 那么, 他们都是存储到一个哈希表内了么?
其实不是的, 变量存储也有作用域的概念. 全局变量被存储到了全局符号表内, 而局部变量也就是指函数或对象内的变量, 则被存储到了活动符号表内 (每个函数或对象都单独维护了自己的活动符号表. 活动符号表的生命周期, 从函数或对象被调用时开始, 到调用完成时结束)
变量销毁
变量销毁, 分为以下几种情况:
1, 手动销毁
2, 垃圾回收机制销毁 (引用计数清 0 销毁和根缓冲区满后销毁)
我们这次主要讲一下手动销毁, 即 unset, 每次销毁时都会将符号表内的变量名和对应的 zval 结构进行销毁, 并将对应的内存归还到 PHP 所维护的内存池内 (按内存大小划分到对应内存列表中)
而对于垃圾回收机制的销毁, 如果你不了解其相关原理, 那么我建议你看下我之前写的文章 PHP 底层原理之垃圾回收机制
思考
今天, 我们从底层的角度, 将变量从生成到销毁讲了一遍. 对于变量的生成, 我们是拿常量赋值作为示例讲解的, 那么变量之间的赋值呢? 是什么原理呢? 且听下回分解~
来源: https://juejin.im/post/5c8e24c2e51d4536485a1070