PHP赋值的内部是如何跑的详解


Posted in PHP onJanuary 13, 2019

前言

在PHP中,一个变量被赋值,内部到底经历了怎样的逻辑判断呢?

PHP在内核中是通过zval这个结构体来存储变量的,它的定义在Zend/zend.h文件里

struct _zval_struct {zvalue_value value; /* 变量的值 */zend_uint refcount__gc;zend_uchar type; /* 变量当前的数据类型 */zend_uchar is_ref__gc;};typedef struct _zval_struct zval;//在Zend/zend_types.h里定义的:typedef unsigned int zend_uint;typedef unsigned char zend_uchar;

使用xdebug的xdebug_debug_zval函数可以打印出变量的refcount,is_ref的值。

$a = 'Hello World';$b = $a;

以上内容在内核中怎么执行呢?

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); //这句很特殊,我们显式的增加了helloval结构体的refcountzend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval, sizeof(zval*), NULL);

可以看出来,当变量赋值的时候,其实两个变量指向的是同一个地址空间。那么问题来了,如果指向同一个地址空间,那不是修改a,b也会跟着改变。这就涉及php的 写时复制机制 。 以上代码,如果后面一行为 $b = '123' 判断过程如下:

  • 如果这个变量的zval部分的refcount小于2,代表没有别的变量在用,则直接修改这个值
  • 否则,复制一份zval 的值,减少原zval的refcount的值,初始化新的zval的refcount,修改新复制的zval

简单变量

先引用赋值后普通赋值

var_dump(memory_get_usage());$a = '1234567890';xdebug_debug_zval('a');var_dump(memory_get_usage());$b = &$a;xdebug_debug_zval('a','b');var_dump(memory_get_usage());$c = $a;xdebug_debug_zval('a','b','c');var_dump(memory_get_usage());$a = '1234567890';var_dump(memory_get_usage());$b = &$a;var_dump(memory_get_usage());$c = $a;

输出内容如下:

int(121672)
a: (refcount=1, is_ref=0)='1234567890'

int(121776)
a: (refcount=2, is_ref=1)='1234567890'
b: (refcount=2, is_ref=1)='1234567890'

int(121824)
a: (refcount=2, is_ref=1)='1234567890'
b: (refcount=2, is_ref=1)='1234567890'
c: (refcount=1, is_ref=0)='1234567890'

int(121928)

$a 赋值,开辟了104byte空间,变量a refcount=1,is_ref=0

$b 赋值,开辟了48byte空间,变量a refcount=2,is_ref=1。48byte是符号表占用,a,b执行同一个地址空间

$c 赋值,开辟了104byte空间。由于a,b是引用,所以在c赋值的时候,会开辟新空间,复制a zval内容,并初始化refcount,is_ref,所以a 的refcount不变,c 的refcount=1

先普通赋值后引用赋值

var_dump(memory_get_usage());$a = '1234567890';xdebug_debug_zval('a');var_dump(memory_get_usage());$b = $a;xdebug_debug_zval('a','b');var_dump(memory_get_usage());$c = &$a;xdebug_debug_zval('a','b','c');var_dump(memory_get_usage());

输出内容如下:

int(121672)

a: (refcount=1, is_ref=0)='1234567890'
int(121776)

a: (refcount=2, is_ref=0)='1234567890'
b: (refcount=2, is_ref=0)='1234567890'
int(121824)

a: (refcount=2, is_ref=1)='1234567890'
b: (refcount=1, is_ref=0)='1234567890'
c: (refcount=2, is_ref=1)='1234567890'
int(121928)

$a 赋值,开辟了104byte空间,变量a refcount=1,is_ref=0

$b 赋值,开辟了48byte空间,变量a refcount=2,is_ref=1。48byte是符号表占用,a,b指向同一个地址空间

$c 赋值,开辟了104byte空间。由于a,c是引用,需要与b隔离开来,因此会赋值原有的zval,初始化zval,将a,c指向新复制的zval,同时原有的zval refcount-1

数组

$arr = [0=>'one'];
xdebug_debug_zval('arr');
$arr[1] = $arr;

xdebug_debug_zval('arr');

$arr[2] = $arr;
xdebug_debug_zval('arr');
unset($arr[1]);
xdebug_debug_zval('arr');
unset($arr[2]);
xdebug_debug_zval('arr');

输出内容如下:

arr: (refcount=1, is_ref=0)=array ( 0 => (refcount=1, is_ref=0)='one')
)
arr: (refcount=1, is_ref=0)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=1, is_ref=0)=array (
0 => (refcount=2, is_ref=0)='one'
)
)
arr: (refcount=1, is_ref=0)=array (
0 => (refcount=3, is_ref=0)='one',
1 => (refcount=2, is_ref=0)=array (
0 => (refcount=3, is_ref=0)='one'),
2 => (refcount=1, is_ref=0)=array (
0 => (refcount=3, is_ref=0)='one',
1 => (refcount=2, is_ref=0)=array (...)
)
)
arr: (refcount=1, is_ref=0)=array (
0 => (refcount=3, is_ref=0)='one',
2 => (refcount=1, is_ref=0)=array (
0 => (refcount=3, is_ref=0)='one',
1 => (refcount=1, is_ref=0)=array (...)
)
)
arr: (refcount=1, is_ref=0)=array (
0 => (refcount=1, is_ref=0)='one'
)
$arr = [0=>'one'];xdebug_debug_zval('arr');$arr[1] = &$arr;xdebug_debug_zval('arr');$arr[2] = $arr;xdebug_debug_zval('arr');unset($arr[1]);xdebug_debug_zval('arr');unset($arr[2]);xdebug_debug_zval('arr');

输出内容如下:

arr: (refcount=1, is_ref=0)=array (
0 => (refcount=1, is_ref=0)='one'
)
arr: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
arr: (refcount=3, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=3, is_ref=1)=...,
2 => (refcount=2, is_ref=0)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=3, is_ref=1)=...,
2 => (refcount=2, is_ref=0)=...)
)
arr: (refcount=2, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one',
2 => (refcount=2, is_ref=0)=array (
0 => (refcount=2, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...,
2 => (refcount=2, is_ref=0)=...)
)
arr: (refcount=2, is_ref=1)=array (
0 => (refcount=2, is_ref=0)='one'
)

上面段测试代码很相似,差别只在arr[1]是否是引用赋值。

arr[1]非引用赋值的情况,arr[0]的refcount = 赋值次数+1,执行两次unset之后,arr,arr[0]的refcount都跟开始定义的时候一致。 arr[1]引用赋值的情况,arr[0]的refcount = 非引用赋值次数+1,执行两次unset之后,arr,arr[0] 的refcount都无法回到定义的时候的值。

主要原因在于arr[1]引用赋值,构成一个递归操作。 但是如果,至于这个refcount,真的说不明白。当没有arr[2]赋值的时候,执行unset, arr refcount能回到1 。从下面这张图更加清晰看出内部递归引用

PHP赋值的内部是如何跑的详解

当出现上面这种情况,refcount本该=1,但实际上面没有被设置为1,这种情况就会出现内存泄漏。上面代码循环执行100次,内存从一开始121096 上升到169224,内存占用上升了5k 。

对象

$user = new User();
 $m = $user;
 $user->user ='';
 $user->name = 'sdfsdfs';
 xdebug_debug_zval('user','m');

以上内容输出

(refcount=2, is_ref=0)=class User {
public $name = (refcount=1, is_ref=0)='sdfsdfs';
public $model = (refcount=1, is_ref=0)=NULL;
public $user = (refcount=1, is_ref=0)=''
}
m: (refcount=2, is_ref=0)=class User {
public $name = (refcount=1, is_ref=0)='sdfsdfs';
public $model = (refcount=1, is_ref=0)=NULL;
public $user = (refcount=1, is_ref=0)=''
}

xdebug给出的is_ref=0。refcount与普通变量一直。但是类的赋值是引用赋值。

$user = new User();
 $user->user = $user;
 $user->name = 'sdfsdfs';
 xdebug_debug_zval('user');
 unset($user);

上面内容输出:

user: (refcount=2, is_ref=0)=class User { public $name = (refcount=1, is_ref=0)='sdfsdfs'; public $user = (refcount=2, is_ref=0)=... }

这里由于类的赋值是引用赋值,索引也构成了一个递归操作,这样也会跟数组一样出现内存泄漏的情况。对以下代码个自行100次

$user = new User();
 $user->user = $user;
 $user->name = 'sdfsdfs';
 xdebug_debug_zval('user');
 unset($user);
$user = new User();
 $user->user = new Order();
 $user->name = 'sdfsdfs';
 xdebug_debug_zval('user');
 unset($user);

第一段代码前后内存差1408 byte. 第二段代码差208 byte。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
在字符串中把网址改成超级链接
Oct 09 PHP
一周学会PHP(视频)Http下载
Dec 12 PHP
PHP的变量总结 新手推荐
Apr 18 PHP
PHP 循环删除无限分类子节点的实现代码
Jun 21 PHP
php设计模式之单例模式实例分析
Feb 25 PHP
php建立Ftp连接的方法
Mar 07 PHP
浅谈PHP中Stream(流)
Jun 08 PHP
学习php设计模式 php实现原型模式(prototype)
Dec 07 PHP
PHP用户验证和标签推荐的简单使用
Oct 31 PHP
php获取linux命令结果的实例
Mar 13 PHP
laravel框架添加数据,显示数据,返回成功值的方法
Oct 11 PHP
php使用redis的几种常见操作方式和用法示例
Feb 20 PHP
PHP5.5新特性之yield理解与用法实例分析
Jan 11 #PHP
PHP基于openssl实现的非对称加密操作示例
Jan 11 #PHP
PHP类的自动加载机制实现方法分析
Jan 10 #PHP
PHP验证类的封装与使用方法详解
Jan 10 #PHP
tp5(thinkPHP5)框架数据库Db增删改查常见操作总结
Jan 10 #PHP
tp5(thinkPHP5)框架实现多数据库查询的方法
Jan 10 #PHP
tp5框架使用composer实现日志记录功能示例
Jan 10 #PHP
You might like
IIS php环境配置PHP5 MySQL5 ZendOptimizer phpmyadmin安装与配置
2008/11/18 PHP
PHP 二维数组根据某个字段排序的具体实现
2014/06/03 PHP
php读取远程gzip压缩网页的方法
2014/12/29 PHP
php pdo oracle中文乱码的快速解决方法
2016/05/16 PHP
PHP中“=>
2019/03/01 PHP
Yii2框架自定义类统一处理url操作示例
2019/05/25 PHP
Gambit vs ForZe BO3 第一场 2.13
2021/03/10 DOTA
获取body标签的两种方法
2011/10/13 Javascript
面向对象的Javascript之三(封装和信息隐藏)
2012/01/27 Javascript
js判读浏览器是否支持html5的canvas的代码
2013/11/18 Javascript
js动态添加删除,后台取数据(示例代码)
2013/11/25 Javascript
js+canvas简单绘制圆圈的方法
2016/01/28 Javascript
jQuery绑定事件的四种方式介绍
2016/10/31 Javascript
AngularJS中的按需加载ocLazyLoad示例
2017/01/11 Javascript
javascript编写简易计算器
2017/05/06 Javascript
基于jQuery中ajax的相关方法汇总(必看篇)
2017/11/08 jQuery
Angular之toDoList的实现代码示例
2017/12/02 Javascript
vue 通过下拉框组件学习vue中的父子通讯
2017/12/19 Javascript
JavaScript实现写入文件到本地的方法【基于FileSaver.js插件】
2018/03/15 Javascript
vue改变循环遍历后的数据实例
2019/11/07 Javascript
[26:50]2018完美盛典DOTA2表演赛
2018/12/17 DOTA
在Python中使用列表生成式的教程
2015/04/27 Python
python 默认参数问题的陷阱
2016/02/29 Python
使用 Python 实现文件递归遍历的三种方式
2018/07/18 Python
python实现字符串加密 生成唯一固定长度字符串
2019/03/22 Python
Python基于scipy实现信号滤波功能
2019/05/08 Python
在Python中画图(基于Jupyter notebook的魔法函数)
2019/10/28 Python
Django使用list对单个或者多个字段求values值实例
2020/03/31 Python
Super-Pharm波兰:药房和香水在一个地方
2020/08/18 全球购物
应届生人事助理求职信
2013/11/09 职场文书
优秀英语专业毕业生求职信
2013/11/23 职场文书
师范生求职自荐信
2014/06/14 职场文书
2014年综合治理工作总结
2014/11/20 职场文书
大学生自荐材料范文
2014/12/30 职场文书
Nginx URL重写rewrite机制原理及使用实例
2021/04/01 Servers
在Java中Collection的一些常用方法总结
2021/06/13 Java/Android