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 相关文章推荐
php IP及IP段进行访问限制的代码
Dec 17 PHP
php 需要掌握的东西 不做浮躁的人
Dec 28 PHP
php上的memcache和memcached两个pecl库
Mar 29 PHP
使用php实现截取指定长度
Aug 06 PHP
php对称加密算法示例
May 07 PHP
PHP将URL转换成短网址的算法分享
Sep 13 PHP
Laravel实现表单提交
May 07 PHP
php 数据结构之链表队列
Oct 17 PHP
PHP getDocNamespaces()函数讲解
Feb 03 PHP
PHP的curl函数的用法总结
Feb 14 PHP
PHP中localeconv()函数的用法
Mar 26 PHP
阿里云服务器搭建Php+Apache运行环境的详细过程
May 15 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
php array的学习笔记
2012/05/10 PHP
php一个找二层目录的小东东
2012/08/02 PHP
PHP包含文件函数include、include_once、require、require_once区别总结
2014/04/05 PHP
php定义一个参数带有默认值的函数实例分析
2015/03/16 PHP
LINUX下PHP程序实现WORD文件转化为PDF文件的方法
2016/05/13 PHP
input 输入框获得/失去焦点时隐藏/显示文字(jquery版)
2013/04/02 Javascript
javascript中创建对象的几种方法总结
2013/11/01 Javascript
js动态创建上传表单通过iframe模拟Ajax实现无刷新
2014/02/20 Javascript
jquery操作 iframe的方法
2014/12/03 Javascript
学习JavaScript鼠标响应事件
2015/12/25 Javascript
使用struts2+Ajax+jquery验证用户名是否已被注册
2016/03/22 Javascript
AngularJS 遇到的小坑与技巧小结
2016/06/07 Javascript
jQuery视差滚动效果网页实现方法经验总结
2016/09/29 Javascript
Vue.js 2.0 移动端拍照压缩图片预览及上传实例
2017/04/27 Javascript
详解微信小程序中的页面代码中的模板的封装
2017/10/12 Javascript
jQuery实现checkbox的简单操作
2017/11/18 jQuery
js实现从右往左匀速显示图片(无缝轮播)
2020/06/29 Javascript
详解阿里Node.js技术文档之process模块学习指南
2021/01/04 Javascript
Python编程使用*解包和itertools.product()求笛卡尔积的方法
2017/12/18 Python
python将字符串以utf-8格式保存在txt文件中的方法
2018/10/30 Python
使用Pandas将inf, nan转化成特定的值
2019/12/19 Python
基于Keras的格式化输出Loss实现方式
2020/06/17 Python
python 实现超级玛丽游戏
2020/11/25 Python
CSS3实现的闪烁跳跃进度条示例(附源码)
2013/08/19 HTML / CSS
app内嵌H5 webview 本地缓存问题的解决
2020/10/19 HTML / CSS
华为俄罗斯官方网上商城:购买Huawei手机和平板
2017/04/21 全球购物
优衣库台湾官网:UNIQLO台湾
2019/02/01 全球购物
what is the difference between ext2 and ext3
2013/11/03 面试题
采购主管工作职责
2013/12/12 职场文书
《彭德怀和他的大黑骡子》教学反思
2014/04/12 职场文书
个人自荐书怎么写
2015/03/26 职场文书
2015年学校德育工作总结
2015/04/22 职场文书
Nginx下配置Https证书详细过程
2021/04/01 Servers
Python的flask接收前台的ajax的post数据和get数据的方法
2021/04/12 Python
python使用tkinter实现透明窗体上绘制随机出现的小球(实例代码)
2021/05/17 Python
只用20行Python代码实现屏幕录制功能
2021/06/02 Python