变量结构

zval 结构体

PHP 所有类型的变量在底层都会以 zval 结构体的形式实现 (源码文件Zend/zend.h)

源码根目录搜索

grep -rin --color --include=*.h --include=*.c _zval_struct *

struct _zval_struct {
/* Variable information */
zvalue_value value; /* 变量value值 */
zend_uint refcount__gc; /* 引用计数内存中使用次数,为0删除该变量 */
zend_uchar type; /* 变量类型 */
zend_uchar is_ref__gc; /* 区分是否是引用变量,是引用为1,否则为0 */
};

注:上面zval结构体是 php5.3 版本之后的结构,php5.3 之前因为没有引入新的垃圾回收机制,即 GC,所以命名也没有_gc;而 php7 版本之后由于性能问题所以改写了 zval 结构,这里不再表述

zval 组成

上面结构体内容可以看出每一个 PHP 变量都会由 变量类型value值引用计数次数是否是引用变量 四部分组成

  • type 变量类型

    type 的值为以下常量:
    IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRAY, IS_OBJECT, IS_RESOURCE

  • value

    因为要存储多种类型,所以 value 是一个 union,也由此实现了弱类型

    typedef union _zvalue_value {
    long lval; /* long value */
    double dval; /* double value */
    struct {
    char *val;
    int len;
    } str;
    HashTable *ht; /* hash table value */
    zend_object_value obj;
    } zvalue_value;
  • refcount__gc 引用计数次数

  • is_ref__gc 是否是引用变量

变量类型

看到这里,可能会有小伙伴们问我,PHP 不是有 8 种数据类型吗?但是为什么对应的 zval 的 value 值只有 5 种?

原因是这样的,PHP 出于对内存节省的考虑,所以对于一些变量类型做了复用,并没有一一对应去定义每个变量类型

下面我们看一下 zval 的每个 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 中

zval.type = IS_BOOL
zval.value.lval = 1/0

资源
「资源」对于 PHP 来说属于一个比较特殊的变量,而 PHP 会将每个「资源」对应的「资源标识编号」存储在 zval.value 的 lval 中。常见的资源有:文件句柄、数据库句柄等

zval.type = IS_RESOURCE
zval.value.lval = 资源标识编号

NULL
对于 NULL 来说,就更好理解了,因为本身通过 zval 的 type 值即可区分,所以并没有将 NULL 值存储在 zval 的 value 中

zval.type = IS_NULL

变量生成

PHP 作为一门动态语言,没有先声明变量后赋值的习惯,所以都是拿来一个常量变量直接就进行了赋值,那么是如何实现的呢?

举例:

$name = "new string";

变量容器生成

其实每次变量被常量赋值时,都会对应生成一个变量容器。刚才的例子会生成一个变量容器,容器的 type 是字符串类型,而 value 值则是『new string』,且此时该变量容器的 ref_count 会加 1

变量名和变量容器关联

而变量 name 是如何与变量容器关联起来的呢?其实也是使用了 PHP 的一个内部机制,即 哈希表。每个变量的变量名 和指向 zval 结构的 指针存储 在 哈希表 内,以此实现了变量名到变量容器的映射。

以变量被赋值为常量为例

当变量被重新引用后,指向 zval 结构体的指针也就发生了变化,而之前指向的 zval 结构体由于没有被引用了,所以 refcount 清零。

变量作用域

上面我们提到了「变量名」和「变量容器」映射的概念。对于 PHP 来说,变量有 全局变量局部变量 之分;那么,他们都是存储到一个 哈希表 内了么?

其实不是的,变量存储也有作用域的概念。

全局变量 被存储到了 全局符号表 内,而 局部变量 也就是指函数或对象内的变量,则被存储到了 活动符号表 内(每个函数或对象都单独维护了自己的活动符号表。活动符号表的生命周期,从函数或对象被调用时开始,到调用完成时结束)

变量销毁

变量销毁,分为以下几种情况:
1、手动销毁
2、垃圾回收机制销毁(引用计数清0销毁和根缓冲区满后销毁)

我们这次主要讲一下手动销毁,即 unset,每次销毁时都会将符号表内的 变量名 和它指向的 **变量容器 zval ** 进行销毁,并将对应的内存归还到 PHP 所维护的内存池内(按内存大小划分到对应内存列表中)

而对于垃圾回收机制的销毁,请看下篇文章php5底层原理之垃圾回收机制

参考资料:

PHP底层原理分析(一):PHP变量的底层实现

php底层原理之变量(一)

PHP5底层原理之变量的更多相关文章

  1. PHP5底层原理之垃圾回收机制

    概念 垃圾回收机制 是一种内存动态分配的方案,它会自动释放程序不再使用的已分配的内存块. 垃圾回收机制 可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑. 与之相关的一个概念,内存 ...

  2. Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL

    Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL 1.1. $ 美元字符, php 黑头1 1.2. 默认变量的范围和声明:1 1.3 ...

  3. Servlet底层原理、Servlet实现方式、Servlet生命周期

    Servlet简介 Servlet定义 Servlet是一个Java应用程序,运行在服务器端,用来处理客户端请求并作出响应的程序. Servlet的特点 (1)Servlet对像,由Servlet容器 ...

  4. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  5. 并发之volatile底层原理

    15.深入分析Volatile的实现原理 14.java多线程编程底层原理剖析以及volatile原理 13.Java中Volatile底层原理与应用 12.Java多线程-java.util.con ...

  6. 【Servlet】(1)Servlet简介、Servlet底层原理、Servlet实现方式、Servlet生命周期

    一.Servlet简介 1.Servlet定义: Servlet(Server Applet)是Java Servlet的简称,是为小服务程序或服务连接器,用Java编写的服务器端程序,主要功能在于交 ...

  7. MVC底层原理

    窥探ASP.Net MVC底层原理 实现跨越Session的分布式TempData 1.问题的引出 我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时 ...

  8. iOS底层原理总结 - 探寻block的本质(一)

        面试题 block的原理是怎样的?本质是什么? __block的作用是什么?有什么使用注意点? block的属性修饰词为什么是copy?使用block有哪些使用注意? block在修改NSMu ...

  9. KVC与Runtime结合使用(案例)及其底层原理

    一.KVC 的用法和实践 用法 KVC(Key-value coding)键值编码,顾名思义.额,简单来说,是可以通过对象属性名称(Key)直接给属性值(value)编码(coding)“编码”可以理 ...

随机推荐

  1. 了解Java线程优先级,更要知道对应操作系统的优先级,不然会踩坑

    Java 多线程系列第 6 篇. 这篇我们来看看 Java 线程的优先级. Java 线程优先级 Thread 类中,使用如下属性来代表优先级. private int priority; 我们可以通 ...

  2. Django-多对多关系的三种创建方式-forms组件使用-cookie与session-08

    目录 表模型类多对多关系的三种创建方式 django forms 组件 登录功能手写推理过程 整段代码可以放过来 forms 组件使用 forms 后端定义规则并校验结果 forms 前端渲染标签组件 ...

  3. numpy库使用总结

    numpy study 0x01:n维数组对象ndaarray 存放同类型元素的多维数组 0x02:numpy数据类型 numpy 的数值类型实际上是 dtype 对象的实例,并对应唯一的字符,包括 ...

  4. SpringBoot集成Spring Security入门体验

    一.前言 Spring Security 和 Apache Shiro 都是安全框架,为Java应用程序提供身份认证和授权. 二者区别 Spring Security:重量级安全框架 Apache S ...

  5. 项目管理知识点-结合Enovia项目管理模块

    核心知识域:整体管理.范围管理.进度管理.成本管理.质量管理.信息安全管理 保障域:人力资源管理.合同管理.采购管理.风险管理.信息(文档)管理.配置管理.知识产权管理.法律法规标准规范管理.职业道德 ...

  6. java几个常见的基础错误

    1.String 相等 稍微有点经验的程序员都会用equals比较而不是用 ==,但用equals就真的安全了吗,看下面的代码 user.getName().equals("xiaoming ...

  7. 004-python面向对象,错误,调试和测试

    ---恢复内容开始--- 1.面向对象 面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作 ...

  8. C++常用库函数(1)

    Hello,疯狂的杰克由于大家见面了哦! 今天,给大家介绍一篇很有内涵的文章:C++常用库函数 1.缓冲区操作函数 函数名:memchr 函数原型:void  *memchr(const void * ...

  9. 序列标注(BiLSTM-CRF/Lattice LSTM)

    前言 在三大特征提取器中,我们已经接触了LSTM/CNN/Transormer三种特征提取器,这一节我们将介绍如何使用BiLSTM实现序列标注中的命名实体识别任务,以及Lattice-LSTM的模型原 ...

  10. Java 基础篇之反射

    反射 使用反射获取程序运行时的对象和类的真实信息. 获取 Class 对象 每个类被加载之后,系统会为该类生成一个对应的 Class 对象,通过该 Class 对象可以访问到 JVM 中的这个类. 使 ...