记得网上流传甚广的段子“PHP是世界上最好的语言”,暂且不去讨论是否言过其实,但至少PHP确实有独特优势的,比如它的弱类型,即只需要$符号即可声明变量,使得PHP入手门槛极低,成为大家所青睐的Web服务端语言。那么它的变量是如何实现的呢?我们今天就来学习一下PHP的基本变量。

一、引言

PHP的变量存储在zval结构体中,在执行阶段中编译为op_array时就能看到zval的身影。结构体定义在Zend/zend_types.h中,定义内容如下所示:

struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* 保留字段 */
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
} u2;
};

二、结构体剖析

2.1、zend_value

结构体的第一个变量是zend_value,顾名思义,它其实也是一个结构体,用于存放变量的值,比如整型、浮点型、引用计数、字符串、数组、对象、资源等。zend_value定义了众多类型的指针,但这些类型并不都是变量的类型,有些是给内核自己使用的,比如指针ast、zv、ptr。

typedef union _zend_value {
zend_long lval; /* 整型 */
double dval; /* 浮点型 */
zend_refcounted *counted; /* 引用计数 */
zend_string *str; /* 字符串 */
zend_array *arr; /* 数组 */
zend_object *obj; /* 对象 */
zend_resource *res; /* 资源 */
zend_reference *ref; /* 引用 */
zend_ast_ref *ast; /* 抽象语法树 */
zval *zv; /* zval类型 */
void *ptr; /* 指针类型 */
zend_class_entry *ce; /* class类型 */
zend_function *func; /* function类型 */
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;

2.2、u1

u1是一个联合体,它联合了结构体v和整型type_info。下面我们先来看一下结构体v的构成。

union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* active type */
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;

2.2.1、type

type是指变量的类型,刚在2.1中讲到了zend_value是用来存储变量的值,所以也应该有地方存储变量的类型,而这就是type的职责。以下是PHP定义的所有变量类型,有我们熟知的布尔、NULL、浮点、数组、字符串等类型。也有陌生的undef、indirect、ptr类型,变量类型在下一章中详解,这里不再赘述。

/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10 /* constant expressions */
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12 /* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14 /* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17

2.2.2、type_flags

可以把它理解为子类型,上面提到了变量的类型,这个是针对不同类型的子类型或标记,type_flags一共有以下6种。

/* zval.u1.v.type_flags */
#define IS_TYPE_CONSTANT (1<<0) /* 常量 */
#define IS_TYPE_IMMUTABLE (1<<1) /* 不可变的类型 */
#define IS_TYPE_REFCOUNTED (1<<2) /* 需要引用计数的类型 */
#define IS_TYPE_COLLECTABLE (1<<3) /* 可能包含循环引用的类型 */
#define IS_TYPE_COPYABLE (1<<4) /* 可被复制的类型 */
#define IS_TYPE_SYMBOLTABLE (1<<5) /* 符号表类型 */

2.2.3、const_flags

常量类型的标记,对应的属性为:

/* zval.u1.v.const_flags */
#define IS_CONSTANT_UNQUALIFIED 0x010
#define IS_LEXICAL_VAR 0x020
#define IS_LEXICAL_REF 0x040
#define IS_CONSTANT_CLASS 0x080 /* __CLASS__ in trait */
#define IS_CONSTANT_IN_NAMESPACE 0x100 /* used only in opline->extended_value */

2.2.4、type_info

type_info与结构体v共用内存,修改type_info等同于修改结构体v的值,所以type_info是v中四个char的组合。

2.3、u2

本来使用u1和zend_value就可以表示变量的,没有必要定义u2,但是我们来看一下,如果没有u2,在内存对齐的情况下zval内存大小为16个字节,当联合了u2后依然是占用16个字节。既然有或没有占用内存大小相同,不如用它来记录一些附属信息。下面我们来看下u2都存储了哪些内容。

2.3.1、next

用来解决哈希冲突问题,记录冲突的下一个元素位置。

2.3.2、cache_slot

运行时缓存,在执行函数时回去缓存中查找,若缓存中没有则到全局function表中查找。

2.3.3、lineno

文件执行的行号,应用在AST节点上。Zend引擎在词法和语法解析时会把当前执行的文件行号记录下来,记录在zend_ast中的lineno中。

2.3.4、num_args

函数调用时传入函数的参数个数。

2.3.5、fe_pos

用于遍历数组时记录当前遍历的位置,比如每次执行foreach时fe_pos都会加一,当再次调用foreach进行遍历时,fe_post会进行重置。

2.3.6、fe_iter_idx

这个与fe_pos类似,只不过它是针对对象的。对象的属性也是HashTable,传入的参数是对象时,会获取对象的属性,所以遍历对象就是在变量对象的属性。

三、参考文献

PHP7内核(六):变量之zval的更多相关文章

  1. 《PHP内核剖析 - 变量/内存管理》

    本文总结自: <PHP7 内核剖析 - 变量的内部实现> 一:变量的实现 - 变量是一个语言实现的基础. - 在PHP中,变量的组成部分为 变量名(zval) 变量值(zend_value ...

  2. 跟厂长学PHP7内核(六):变量之zval

    记得网上流传甚广的段子"PHP是世界上最好的语言",暂且不去讨论是否言过其实,但至少PHP确实有独特优势的,比如它的弱类型,即只需要$符号即可声明变量,使得PHP入手门槛极低,成为 ...

  3. 跟厂长学PHP7内核(七):常见变量类型的基本结构

    上篇文章讲述了变量的存储结构zval,今天我们就来学习一下几个常见变量类型的基本结构. 一.类型一览 zval中的u1.v.type用来存储变量的类型,而zval.value存储的是不同类型对应的值, ...

  4. 深入剖析PHP7内核源码(二)- PHP变量容器

    简介 PHP的变量使用起来非常方便,其基本结构是底层实现的zval,PHP7采用了全新的zval,由此带来了非常大的性能提升,本文重点分析PHP7的zval的改变. PHP5时代的ZVAL typed ...

  5. PHP7内核(七):常见变量类型的基本结构

    上篇文章讲述了变量的存储结构zval,今天我们就来学习一下几个常见变量类型的基本结构. 一.类型一览 zval中的u1.v.type用来存储变量的类型,而zval.value存储的是不同类型对应的值, ...

  6. 跟厂长学PHP7内核(八):深入理解字符串的实现

    在前面大致预览了常用变量的结构之后,我们今天来仔细的剖析一下字符串的具体实现. 一.字符串的结构 struct _zend_string { zend_refcounted_h gc; /* 字符串类 ...

  7. PHP7内核(八):深入理解字符串的实现

    在前面大致预览了常用变量的结构之后,我们今天来仔细的剖析一下字符串的具体实现. 一.字符串的结构 struct _zend_string { zend_refcounted_h gc; /* 字符串类 ...

  8. 深入理解PHP内核(七)变量及数据类型-常量

    原文链接:http://www.orlion.ga/246/ 在PHP中,常量的名字是一个简单值的标识符,在脚本执行期间该值不能改变.和变量一样,常量默认为大小写敏感,但是通常是大写的. 常量是在变量 ...

  9. 深入理解PHP内核(五)变量及数据类型-变量的结构和类型

    原文链接:http://www.orlion.ga/238/ 编程语言的类型可以分为强类型和弱类型两种,PHP是弱类型语言,但是C语言是强类型语言.在官网PHP实现内部,所有变量使用同一种数据结构(z ...

随机推荐

  1. 从头认识js-函数表达式

    定义函数的方式有两种: 1.函数声明(特征:函数声明提升,在执行代码之前会先读取函数声明,这就意味着可以把函数声明放在调用它的语句之后) 2.函数表达式(函数表达式与其他表达式一样,使用之前必须先声明 ...

  2. 《数字信号处理》课程实验1 – FFT的实现

    一.按时间抽选的基-2 FFT实现原理 观察DIT(基2)FFT的流图(N点,N为2的幂次),可以总结出如下规律: (1)共有\(L=\log_2⁡N\)级蝶形运算: (2)输入倒位序,输出自然顺序: ...

  3. UBB代码

    UBB代码是HTML(标准通用标记语言下的一个应用)的一个变种,是Ultimate Bulletin Board (国外的一个BBS程序)采用的一种特殊的TAG.您也许已经对它很熟悉了.UBB代码很简 ...

  4. YiGo表单建立

    做一个请假单表单(下图是最后的成品图) 表单的类型 实体表单 1.可存储 2.可编辑 虚拟表单 视图(不可存储数据,只有显示功能) 不可编辑 字典 报表 备注 :一张表单是实体还是虚拟取决于其数据对象 ...

  5. Android模拟器不能上网的解决方法

    我原来一直不用Android的模拟器,因为这东西的多年前的印象真的是很糟糕——启动半个小时,不支持OpenGL.即使后来有了x86镜像,在HAXM的支持下快的飞起,也不想用,因为NDK还要编译x86的 ...

  6. webpack知识锦集(一)

    ebpack是一个javascript应用吃那个程序的静态模块打包器(module bundler).处理时候会递归构建一个依赖关系图,包含每个模块,将模块打包成一个或者多个bundle. 核心概念: ...

  7.  FPGA边沿检测Verilog代码

    FPGA边沿检测Verilog代码(上升沿,下降沿,双边沿) 实现思路:用两个一位寄存器直接异或可以实现 代码实现: ​ module edge_detect( input clk, input rs ...

  8. layui表格数据渲染SpringBoot+Thymeleaf返回的数据时报错(Caused by: org.attoparser.ParseException: Could not parse as expression: ")

    layui table渲染数据时报错(Caused by: org.attoparser.ParseException: Could not parse as expression: ") ...

  9. django 引入静态文件(前端样式等)

    1.首先在主项目目录下settings.py文件中添加如下代码 2.然后在主项目目录,app项目同级目录下新建static文件夹并在文件夹中新建css文件夹,用于存放css文件,如下: 3.前端渲染时 ...

  10. 关于动态路由中路由之间的跳转(页面a跳转到页面b)

    由addRouters方法获取到后台的动态路由,要实现路由之间的跳转,不可直接用path: '***',而是将动态路由存储到vuex中,再从vuex中取得,如:this.$store.menu.nav ...