变量改变时PHP内核做了些什么?
引言
内容来自于《Extending and Embedding PHP》- Chaper 3 - Memory Management,加上自己的理解,对php中变量的引用计数、写时复制,写时改变,写时复制和改变做个”翻译“。
zval
看下面的内容之前先对zval这个结构体做个了解
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;
zval结构体中共有4个元素,value是一个联合体,用来真正的存储zval的值,refcount用来计数该zval被多少个变量使用,type表示zval所存储的数据类型,is_ref用来标志该zval是否被引用。
引用计数
<?php
$a = 'Hello World';
$b = $a;
unset($a);
?>
我们一起来剖析下上面这段代码:
$a = 'Hello World';首先这句代码被执行,内核创建一个变量,并分配12字节的内存去存储字符串'Hello World'和末尾的NULL。$b = $a;接着执行这句代码,执行这句的时候内核里面发生了什么呢?对
$a所指向的zval中的refcount进行加1操作。将变量
$b指向$a所指向的zval。
在内核中大概是这样的,其中active_symbol_table是当前的变量符号表{
zval *helloval;
MAKE_STD_ZVAL(helloval);
ZVAL_STRING(helloval, "Hello World", 1);
zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),
&helloval, sizeof(zval*), NULL);
ZVAL_ADDREF(helloval);
zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),
&helloval, sizeof(zval*), NULL);
}
unset($a);这句代码执行后,内核会将\(a对应的zval结构体中的refcount计数减一,\)b还和原来一样
写时复制
<?php
$a = 1;
$b = $a;
$b += 5;
?>
上面这段代码执行完之后,一般肯定希望$a=1,$b=6,但是如果像引用计数那样,$a和$b指向相同的zval,修改$b之后$a不是也变了?
这个具体是怎么实现的呢,我们一起来看下:
$a = 1;内核创建一个zval,并分配4个字节存储数字1。$b = $a;这一步和引用计数中的第二步一样,将$b指向和$a相同的zval,并将zval中的引用计数值refcount加1。$b += 5;关键是这一步,这一步骤发生了什么呢,怎么确保修改之后不影响$a。其实Zend内核在改变zval之前都会去进行
get_var_and_separete操作,如果recfount>1,就需要分离就创建新的zval返回,否则直接返回变量所指向的zval,下面看看如何分离产生新的zval。复制一个和
$b所指向zval一样的zval。将
$b所指向的zval中的refcount计数减1。初始化生成的新zval,设置refcount=1,is_ref=0。
让
$b指向新生成的zval。对新生成的zval进行操作,这就是写时复制。
下面看看内核中分离时的主要代码:zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
{
zval **varval, *varcopy;
if (zend_hash_find(EG(active_symbol_table),
varname, varname_len + 1, (void**)&varval) == FAILURE) {
/* Variable doesn't actually exist fail out */
return NULL;
}
if ((*varval)->is_ref || (*varval)->refcount < 2) {
/* varname is the only actual reference,
* or it's a full reference to other variables
* either way: no separating to be done
*/
return *varval;
}
/* Otherwise, make a copy of the zval* value */
MAKE_STD_ZVAL(varcopy);
varcopy = *varval;
/* Duplicate any allocated structures within the zval* */
zval_copy_ctor(varcopy); /* Remove the old version of varname
* This will decrease the refcount of varval in the process
*/
zend_hash_del(EG(active_symbol_table), varname, varname_len + 1); /* Initialize the reference count of the
* newly created value and attach it to
* the varname variable
*/
varcopy->refcount = 1;
varcopy->is_ref = 0;
zend_hash_add(EG(active_symbol_table), varname, varname_len + 1,
&varcopy, sizeof(zval*), NULL);
/* Return the new zval* */
return varcopy;
}
写时改变
<?php
$a = 1;
$b = &$a;
$b += 5;
?>
上面这段代码执行完之后一般希望是:$a == $b == 6。这个又是怎么实现的呢?
$a = 1;这一步骤和写时复制中的第一步一样。$b = &$a;这一步骤内核会将$b指向$a所指向的zval,将zval中的refcount加1,并将zval中的is_ref置为1。$b += 5;这一步骤和写时复制中的第三步骤一样,但是内核中发生的事情却不一样。- 内核看到
$b进行变化的时候,也会执行get_var_and_separate函数,看是否需要分离。 - 如果
(*varval)->is_ref的话也会直接返回$b所指向的zval,不去分离产生新的zval,不管zval的refcount是否>1。 - 这时候再去修改
$b值,$a的值也就改变了,因为他们指向相同的zval。
- 内核看到
分离的问题
说道现在聪明的你可能已经看出点问题了,如果一个zval结构体既有refcount计数又有is_ref引用这个时候怎么办?
<?php
$a = 1;
$b = $a;
$c = &$a;
?>
如果出现上面这种情况的时候,如果$a、$b、$c指向同一个zval结构体,进行改变的时候Zend到底去听谁的?其实这个地方不会指向同一个zval了。
如果对一个is_ref = 0 && refcount >1的zval进行写时改变这种赋值形式(就是引用赋值)的时候,Zend会将等号右边的变量分离出来一个新的zval,
对这个zval进行初始化,对之前的zval的refcount进行减1操作,让等号左边的变量指向这个新的zval,refcount进行加1操作,is_ref=1。看看下面这张图片

<?php
$a = 1;
$b = &$a;
$c = $a;
?>
上面这又是另外一种情况,在is_ref = 1的情况下,试图单纯的进行refcount+1操作的时候会分离出来一个新的zval给等号左边的变量,并初始化他,看看下面这张图片

global和$GLOBALS['']
这两个用的比交多的都是将全局作用域下的变量引入到函数作用域,但是他们之间是有一点差距的。
- global \(a 是全局作用域的\)a的引用。
- \(GLOBALS['a'] 是全局作用域的\)a变量本身。
参考文献
1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.
2. PHP中global与$GLOBALS['']区别
微信号: love_skills
越努力,越幸运!越幸运,越努力!
做上CEO不是梦
赢取白富美不是梦
屌丝逆袭不是梦
就是现在!!加油

变量改变时PHP内核做了些什么?的更多相关文章
- linux中,当执行rpm -e删除一个软件包时,都做了些什么事
问题描述: 今天在通过rpm进行删除软件包时,出现了问题,就引发了我对于rpm包执行删除动作时的一些行为做了思考,之前找了很多的文章,后来想如果有debug日志信息,那么不就都清楚了吗 通过打印rpm ...
- 从架构演进的角度聊聊Spring Cloud都做了些什么?
Spring Cloud作为一套微服务治理的框架,几乎考虑到了微服务治理的方方面面,之前也写过一些关于Spring Cloud文章,主要偏重各组件的使用,本次分享主要解答这两个问题:Spring Cl ...
- 转Rollback后undo到底做了些什么?
转自:http://biancheng.dnbcw.info/oracle/309191.html Rollback后undo到底做了些什么? 从概念上讲,undo正好与redo相对.当你对数据执行修 ...
- WdatePicker日历添加事件,在任意月改变时处理日期事件
原由 在做系统时根据要求有时候需要屏蔽掉某些特殊的日期,像周日或者法定假日,以及一些调班的日期:使用WdatePicker可以屏蔽掉周日和大多数法定假日,但像清明或者调班的日期则不好处理. 想法 1: ...
- 【小梅哥SOPC学习笔记】NIOS II工程目录改变时project无法编译问题
解决NIOS II工程移动在磁盘上位置后project无法编译问题 说明:本文档于2017年3月4日由小梅哥更新部分内容,主要是增加了讲解以Quartus II13.0为代表的经典版本和以15.1为代 ...
- 【dotnet跨平台】"dotnet restore"和"dotnet run"都做了些什么?
[dotnet跨平台]"dotnet restore"和"dotnet run"都做了些什么? 前言: 关于dotnet跨平台的相关内容.能够參考:跨平台.NE ...
- [转帖]支撑双11每秒17.5万单事务 阿里巴巴对JVM都做了些什么?
支撑双11每秒17.5万单事务 阿里巴巴对JVM都做了些什么? https://mp.weixin.qq.com/s?__biz=MzA3OTg5NjcyMg==&mid=2661671930 ...
- EditText设置文字改变时的监听
textWatcher = new TextChangeWatcher(); etQuerryInfo.addTextChangedListener(textWatcher); /** * 文字改变类 ...
- loadView在App启动时到底都干了些什么?
loadView在App启动时到底都干了些什么? 查阅苹果官方文档如下: 1. 当你访问一个ViewController的view属性时,如果此时view的值是nil,那么,ViewControlle ...
随机推荐
- 请问utf-8的中文是一个汉字占三个字节长度吗?
这是个好问题,可以当作一个笔试题.先从字符编码讲起. 1.美国人首先对其英文字符进行了编码,也就是最早的ascii码,用一个字节的低7位来表示英文的128个字符,高1位统一为0: 2.后来欧洲人发现尼 ...
- Play Framework 完整实现一个APP(十二)
1.定制CRUD管理页面 > play crud:ov --layout 替换生成文件内容 app/views/CRUD/layout.html #{extends 'admin.html' / ...
- Linux mysql 5.6: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
案例环境: 操作系统 :Red Hat Enterprise Linux Server release 5.7 (Tikanga) 64 bit 数据库版本 : Mysql 5.6.19 64 bit ...
- ORA-27125: unable to create shared memory segment
平台环境 : Oracle Linux Server release 5.7 x86_64 数据库版本 : Oracle Database 10g Enterprise Edition Rel ...
- MongoDB学习笔记~大叔框架实体更新支持N层嵌套~递归递归我爱你!
回到目录 递归递归我爱你!只要你想做,就一定能成功! 从一到二,从二到三,它是容易的,也是没什么可搞的,或者说,它是一种流水线的方式,而从三到十,从十到百,它注定要有一个质的突破,否则,它会把你累死, ...
- Asp.Net MVC+BootStrap+EF6.0实现简单的用户角色权限管理3
首先在webconfig中加入下面这句代码,这个主要是用来生成数据库的连接字符串 <connectionStrings> <add name="AuthorDesignCo ...
- sql server全文索引使用中的小坑
一.业务场景 我们在实际生产环境中遇到了这样一种需求,即需要检索一个父子关系的子树数据 估计大家也遇到过类似的场景,最典型的就是省市数据,其中path字段是按层级关系生成的行政区路径: 如果我们已知某 ...
- Java 多态
多态通过分离做什么和怎么做,从另一个角度将接口和实现分离开来. 继承允许将对象视为它自己本身的类型活基类型来加以处理. 方法调用绑定 绑定: 将一个方法调用同一个方法主体关联起来. 前期绑定:在程序执 ...
- 每天一个linux命令(2):cd命令
1. 命令格式: cd [目录名] 2. 命令功能: 切换当前目录至dirName 3. 常用范例 3.1 例一:进入系统根目录 命令: cd / 输出: [root@localhost ~]# cd ...
- C# 扩展方法集
语法注意点 可以使用扩展方法来扩展类或接口. 不能重写扩展方法. 扩展方法只能在非嵌套.非泛型静态类内部定义. 扩展方法必须定义在静态类中. 扩展方法的第一个参数的类型用于指定被扩展的类型,它限制该扩 ...