php变量的实现
1.php变量的实现
变量名 zval ,变量值 zend_value,php7的变量内存管理的引用计数 在zend_value结构上,变量的操作也都是zend_value实现的。
//zend_types.h
typedef struct _zval_struct zval; typedef union _zend_value {
zend_long lval; //int整形
double dval; //浮点型
zend_refcounted *counted;
zend_string *str; //string字符串
zend_array *arr; //array数组
zend_object *obj; //object对象
zend_resource *res; //resource资源类型
zend_reference *ref; //引用类型,通过&$var_name定义的
zend_ast_ref *ast; //下面几个都是内核使用的value
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value; struct _zval_struct {
zend_value value; //变量实际的value
union {
struct {
ZEND_ENDIAN_LOHI_4( //这个是为了兼容大小字节序,小字节序就是下面的顺序,大字节序则下面4个顺序翻转
zend_uchar type, //变量类型
zend_uchar type_flags, //类型掩码,不同的类型会有不同的几种属性,内存管理会用到
zend_uchar const_flags,
zend_uchar reserved) //call info,zend执行流程会用到
} v;
uint32_t type_info; //上面4个值的组合值,可以直接根据type_info取到4个对应位置的值
} u1;
union {
uint32_t var_flags;
uint32_t next; //哈希表中解决哈希冲突时用到
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; //一些辅助值
};
zval
结构比较简单,内嵌一个union类型的zend_value
保存具体变量类型的值或指针,zval
中还有两个union:u1
、u2
:
u1: 它的意义比较直观,变量的类型就通过u1.v.type
区分,另外一个值type_flags
为类型掩码,在变量的内存管理、gc机制中会用到,第三部分会详细分析,至于后面两个const_flags
、reserved
暂且不管
u2: 这个值纯粹是个辅助值,假如zval
只有:value
、u1
两个值,整个zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些特殊用途还是很划算的,比如next在哈希表解决哈希冲突时会用到,还有fe_pos在foreach会用到......
从zend_value
可以看出,除long
、double
类型直接存储值外,其它类型都为指针,指向各自的结构。
zval.u1.type
类型
/* 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
简单的类型是true、false、long、double、null,其中true、false、null没有value,直接根据type区分,而long、double的值则直接存在value中
字符串结构
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[];
};
- gc: 变量引用信息,比如当前value的引用数,所有用到引用计数的变量类型都会有这个结构,3.1节会详细分析
- h: 哈希值,数组中计算索引时会用到
- len: 字符串长度,通过这个值保证二进制安全
- val: 字符串内容,分配时按len长度申请内存
字符串分类:
IS_STR_PERSISTENT(通过malloc分配的)
IS_STR_INTERNED(php代码里写的一些字面量,比如函数名、变量值)
IS_STR_PERMANENT(永久值,生命周期大于request)
IS_STR_CONSTANT(常量)
IS_STR_CONSTANT_UNQUALIFIED
数组结构 php数组的底层结构是hashTabe (php内核的函数 类 文件索引表 全局符号表页都是用hash结构实现的)
hash结构根据hash码进行访问的key-value数据结构,根据key映射函数直接索引到value,采用直接寻址直接映射key到内存地址,查找的期望时间O(1)
typedef struct _zend_array HashTable; struct _zend_array {
zend_refcounted_h gc; //引用计数信息,与字符串相同
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar reserve)
} v;
uint32_t flags;
} u;
uint32_t nTableMask; //计算bucket索引时的掩码
Bucket *arData; //bucket数组
uint32_t nNumUsed; //已用bucket数
uint32_t nNumOfElements; //已有元素数,nNumOfElements <= nNumUsed,因为删除的并不是直接从arData中移除
uint32_t nTableSize; //数组的大小,为2^n
uint32_t nInternalPointer; //数值索引
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
arData
指向存储元素数组的第一个Bucket,插入元素时按顺序 依次插入 数组,比如第一个元素在arData[0]、第二个在arData[1]...arData[nNumUsed]。PHP数组的有序性正是通过arData
保证的,arData并不是按key映射的散列表,列表在ht->arData内存之前,分配内存时这个散列表与Bucket数组一起分配,arData向后移动到了Bucket数组的起始位置,并不是申请内存的起始位置,这样散列表可以由arData指针向前移动访问到,即arData[-1]、arData[-2]、arData[-3]......散列表的结构是uint32_t
,它保存的是value在Bucket数组中的位置。
hash碰撞 不同的key可能计算得到相同的哈希值(数值索引的哈希值直接就是数值本身),但是这些值又需要插入同一个散列表。一般解决方法是将Bucket串成链表,查找时遍历链表比较key。
数组扩容 容量不够时检查 删除元素的比例,达到一个阈值时重建索引,没有达到时直接扩容为当前大小的两倍,复制bucket到新的内存空间重建索引
重建散列表 当删除元素达到一定数量或扩容后都需要重建散列表,因为value在Bucket位置移动了或哈希数组nTableSize变化了导致key与value的映射关系改变,重建过程实际就是遍历Bucket数组中的value,然后重新计算映射值更新到散列表。
hash元素删除 一个元素从哈希表删除时并不会将对应的Bucket移除,而是将Bucket存储的zval修改为IS_UNDEF
,只有扩容时发现nNumOfElements与nNumUsed相差达到一定数量(这个数量是:ht->nNumUsed - ht->nNumOfElements > (ht->nNumOfElements >> 5)
)时才会将已删除的元素全部移除,重新构建哈希表 。
对象/资源结构
struct _zend_object {
zend_refcounted_h gc;
uint32_t handle;
zend_class_entry *ce; //对象对应的class类
const zend_object_handlers *handlers;
HashTable *properties; //对象属性哈希表
zval properties_table[];
}; struct _zend_resource {
zend_refcounted_h gc;
int handle;
int type;
void *ptr;
};
对象比较常见,资源指的是tcp连接、文件句柄等等类型,这种类型比较灵活,可以随意定义struct,通过ptr指向。
引用类型结构
struct _zend_reference {
zend_refcounted_h gc;
zval val;
};
引用是PHP中比较特殊的一种类型,它实际是指向另外一个PHP变量,对它的修改会直接改动实际指向的zval,可以简单的理解为C中的指针,但是PHP中的 引用只可能有一层 ,不会出现一个引用指向另外一个引用的情况 ,也就是没有C语言中指针的指针
的概念。
2.内存管理
引用计数 使用的是refcount字段,变量复制和函数传参的时候直接refcount++,销毁变量refcount--,refcount=0销毁变量。
简单的标量数据类型不会用到引用计数直接是硬拷贝,只有value是指针的数据类型使用引用计数。
字符串的内部字符串类型不会用到引用计数,函数名 类名 静态字符串 变量名等字面量都是这种数据类型
写时复制
引用计数之后,如果其中一个变量试图更改value的内容则会重新拷贝一份value修改,同时断开旧的指向。
变量回收 主动销毁(unset)、自动销毁()
PHP变量的回收是根据refcount实现的,当unset、return时会将变量的引用计数减掉,如果refcount减到0则直接释放value,这是变量的简单gc过程,但是实际过程中array、object两种类型中出现gc无法回收导致内存泄漏的bug。
当销毁一个变量时,如果发现减掉refcount后仍然大于0,且类型是IS_ARRAY、IS_OBJECT则将此value放入gc可能垃圾双向链表(缓冲buffer)中,等这个链表达到一定数量(10000个值)后启动检查程序将所有变量检查一遍,如果确定是垃圾则销毁释放。
遍历 buffer链表,把value标记 灰色-》 深度遍历value成员 refcount-- 标记灰色 -》 再次遍历buffer链表检查value的refcount是否是0 -》 是0表示是垃圾标记为白色,不是0表示不是垃圾 -》深度遍历不是0的value把所有成员变量的refcount++标记为黑色 -〉遍历buffer把白色的几点删除 清除这些垃圾
php变量的实现的更多相关文章
- 6 小时 Python 入门
6 小时 Python 入门 以下操作均在 Windows 环境下进行操作,先说明一下哈 一.安装 Python 1.官网下载 Python 进入官网(https://www.python.org), ...
- In-Memory:在内存中创建临时表和表变量
在Disk-Base数据库中,由于临时表和表变量的数据存储在tempdb中,如果系统频繁地创建和更新临时表和表变量,大量的IO操作集中在tempdb中,tempdb很可能成为系统性能的瓶颈.在SQL ...
- 探究javascript对象和数组的异同,及函数变量缓存技巧
javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...
- Shell特殊变量
$ 表示当前Shell进程的ID,即pid $echo $$ 运行结果 特殊变量列表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数 ...
- Java多线程基础——对象及变量并发访问
在开发多线程程序时,如果每个多线程处理的事情都不一样,每个线程都互不相关,这样开发的过程就非常轻松.但是很多时候,多线程程序是需要同时访问同一个对象,或者变量的.这样,一个对象同时被多个线程访问,会出 ...
- Js 变量声明提升和函数声明提升
Js代码分为两个阶段:编译阶段和执行阶段 Js代码的编译阶段会找到所有的声明,并用合适的作用域将它们关联起来,这是词法作用域的核心内容 包括变量声明(var a)和函数声明(function a(){ ...
- PHP源码分析-变量
1. 变量的三要素变量名称,变量类型,变量值 那么在PHP用户态下变量类型都有哪些,如下: // Zend/zend.h #define IS_NULL 0 #define IS_LONG 1 #de ...
- JavaScript var关键字、变量的状态、异常处理、命名规范等介绍
本篇主要介绍var关键字.变量的undefined和null状态.异常处理.命名规范. 目录 1. var 关键字:介绍var关键字的使用. 2. 变量的状态:介绍变量的未定义.已定义未赋值.已定义已 ...
- shell变量
定义变量 定义变量时,变量名不加美元符号($),如: variableName="value" 注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样.同时,变量名 ...
- 引人瞩目的 CSS 变量(CSS Variable)
这是一个令人激动的革新. CSS 变量,顾名思义,也就是由网页的作者或用户定义的实体,用来指定文档中的特定变量. 更准确的说法,应该称之为 CSS 自定义属性 ,不过下文为了好理解都称之为 CSS 变 ...
随机推荐
- Tomcat灵活配置多项目,多端口,多域名,多虚拟目录
Tomcat的配置都在Tomcat的安装目录的conf文件夹下的server.xml文件 最初内容:(去掉所有注释) <?xml version="1.0" encoding ...
- Python - TypeError: unicode argument expected, got 'str'
参考:TypeError: unicode argument expected, got 'str' Python代码: from io import StringIO def main(): f = ...
- Python学习札记(三十九) 面向对象编程 Object Oriented Program 10
参考:使用枚举类 NOTE #!/usr/bin/env python3 from enum import Enum def main(): Mouth = Enum('Mouth', ('Jan', ...
- Linux配置NFS实现共享
(1)安装相应rpm包 sudo rpm -ivh nfs-utils-1.2.3-70.el6.x86_64.rpm (2)配置共享目录:sudo vim /etc/exports /app/sof ...
- Codeforces Round #394 (Div. 2) B. Dasha and friends(暴力)
http://codeforces.com/contest/761/problem/B 题意: 有一个长度为l的环形跑道,跑道上有n个障碍,现在有2个人,给出他们每过多少米碰到障碍,判断他们跑的是不是 ...
- 使用javascript模拟常见数据结构(三)
六.字典和散列表 我们已经知道,集合表示一组互不相同的元素(不重复元素).在字典中,存储的是键值对,其中键值是用来查询特定的元素的.字典和集合是很相似的,集合采用[值,值]的方式存储,而字典则是以[键 ...
- 菜单项onCreateOptionsMenu()和onOptionsItemSelected()的使用
Java源文件 package com.example.macname.myapplication; import android.support.v7.app.AppCompatActivity; ...
- Android中Tablayout设置下划线宽度 和 dp和px之间进行相互转换
开发中遇到了一个问题,Tablayout设置下换线长度,看了点资料,分享给大家. 效果图: 直接贴代码(要在tabLayout添加完所有的tab后调用) public vo ...
- Leetcode 52
//N皇后的基础上改了一点class Solution { public: int totalNQueens(int n) { ; vector<); DFS(pos,,res); return ...
- 【Python】operator 模块简单介绍
简单介绍几个常用的函数,其他的请参考文档. operator.concat(a, b) **operator.__concat__(a, b)** 对于 a.b序列,返回 a + b(列表合并) -- ...