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

前言

在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 。从下面这张图更加清晰看出内部递归引用

当出现上面这种情况,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。

总结

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

(0)

相关推荐

  • php foreach 使用&(与运算符)引用赋值要注意的问题

    foreach 通过在 $value 之前加上 & 很容易就能修改数组的单元,如: PHP代码 复制代码 代码如下: foreach($arr as $value){ $value .= '4'; } 但这个用法也很容易造成错误,而且也不容易找. 看例子更直截了当: PHP代码 复制代码 代码如下: <?php $arr = array('a','b','c'); $arr2 = array('d', 'e', 'f'); foreach($arr as $value){//习惯用$valu

  • PHP基础陷阱题(变量赋值)

    复制代码 代码如下: <?php $a=3; $b=6; if($a=5||$b=7){ $a++; $b++; } var_dump($a, $b); 陷阱一 把$a=5.$b=7看成了$a==5.$b==7 错误结果:3,6 陷阱二 运算符的优先级,认为$a=5赋值成功$b=7没执行 错误结果:6,7 正确理解 陷阱是运算符的优先,赋值运算符(=)的优先级是最低的,所以正确理解应该是 $a=(5||$b=7) 正确结果:true,7 升级一下 变形一 复制代码 代码如下: $a=3; $b=

  • PHP5 的对象赋值机制介绍

    复制代码 代码如下: <?php class SimpleClass{ public $var = 'a default value'; public function displayVar() { echo $this->var; } } $instance = new SimpleClass(); $assigned = $instance; $reference =& $instance; $instance->var = '$assigned will have this

  • PHP变量赋值、代入给JavaScript中的变量

    复制代码 代码如下: $(document).ready(function(){ <?php $f="'name'"?>     var t=<?php echo $f?>;     alert(t)    }) 或 复制代码 代码如下: $(document).ready(function(){ <?php $f="name"?>     var t="<?php echo $f?>";    

  • php数组操作之键名比较与差集、交集赋值的方法

    本文实例讲述了php数组操作之键名比较与差集.交集赋值的方法.分享给大家供大家参考.具体方法如下: 该实例主要实现对数组的各种常见操作.如对键名比较计算数组的差集,计算差集,给指定数组中插入一个元素,反转数组与交集赋值新的数组等. 具体代码如下: 复制代码 代码如下: //定义回调函数 function key_compare_func($key1,$key2) {   if($key1==$key2)         //如果两参数相等   return 0;          //返回0  

  • php 传值赋值与引用赋值的区别

    传值赋值:当将一个表达式的值赋予一个变量时,整个原始表达式的值被赋予到目标变量.这意味着,例如,当一个变量的值赋予另一个变量时,改变其中一个变量的值,将不会影响到另一个变量. 复制代码 代码如下: <?php <?php $a=123; $a=123; $b=$a; $b=&$a; $a=321; $a=321; Echo"$a,$b";//显示"321,123" Echo"$a,$b";//显示"321,321&q

  • php中给js数组赋值方法

    因为接口方的要求,用js中处理数据,所以需要php程序从数据库取出数值后赋值给js数组.一直没有找到很好的办法,因为PHP数组的数据编码和JS 数组的编码格式不一样,不能直接输出. 在网上搜索一通后,找到的解决的方法: PHP函数库提供了编/解码JSON的函数:json_encode()和json_decode(),可以比较方便的传递数组或对象给javascript.注意:PHP 5.2以上才绑定了JSON扩展. 在php如下写: 复制代码 代码如下: $arr = array('1',arra

  • 详解PHP数组赋值方法

    PHP数组还是比较常用的,于是我研究了一下PHP数组赋值,在这里拿出来和大家分享一下,希望对大家有用. 所谓数组就是一组变量的集合保存在计算机的内存中,这些变量可以是不同的类型,包括整数,布尔值,字符串等.可以说数组就是内存中的一个小型的数据库,它为我们访问数据提供了一个快捷的方式――可以省出IO或者数据库的频繁访问以此来提高一些性能,所以关于数组中一些操作也是很多的. 怎样创建在PHP中创建数组,你可以使用如下方法: 方法之一创建数组: <?php $a="abcd"; pri

  • PHP读取txt文件的内容并赋值给数组的代码

    2010-12-15.txt的文件内容如下: 复制代码 代码如下: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 T01 T02 T03 T04 T05 T06 T07 T08 T09 T10 T11 T12 T13 T14 T15 T16 思路如下:使用file_get_contents()获取txt文件的内容,然后通过explode()把获得的字符串转化为数组.获得数组长度可以使用count()

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

    前言 在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

  • C++继承的赋值转换与菱形虚拟继承深入详解

    目录 一.继承的概念及定义 1.1.继承的概念 1.2.继承的定义 二.基类和派生类对象赋值转换 三.继承中的作用域 3.1.继承同名成员处理方式 3.2.继承同名静态成员处理方式 3.3.继承与友元 3.4.继承与静态成员 四.派生类的默认成员函数 五.复杂菱形继承及菱形虚拟继承 5.1.继承分类 5.2.虚拟继承解决菱形继承问题原理 一.继承的概念及定义 继承是面向对象三大特性之一. 1.1.继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许

  • Python的赋值、深拷贝与浅拷贝的区别详解

    在python中,给一个对象赋值,实际上就是对象对内存空间存储的值的引用.当我们把对象赋值给另一个变量的时候,这个变量并没有拷贝这个对象,而只是拷贝了这个对象的引用而已. 一般情况下我们会通过三种方法来实现拷贝对象的引用. Python直接赋值 直接赋值,默认浅拷贝传递对象的引用而已,原始列表改变,被赋值的变量也会做相同的改变.其实就是对'对象'的引用 示例: >>> list_demo = [2, 4, 6] >>> a = list_demo >>>

  • python dict 字典 以及 赋值 引用的一些实例(详解)

    最近在做一个很大的数据库方面的东东,要用到根据数值来查找,于是想到了python中的字典,平时没用过dict这个东东 用的最多的还是 list 和 tuple (网上查 用法一大堆) 看了一下创建字典的方法: 方法1: dict = {'name': 'earth', 'port': 80} 方法2: fdict = dict((['x', 1], ['y', 2])) 方法3: ddict = {}.fromkeys(('x', 'y'), -1) 都实验了一下这些方法,发现不好用,做不出来自

  • ECMAScript6变量的解构赋值实例详解

    数组的解构赋值 ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring) var [a, b, c] = [1, 2, 3]; 这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值. 下面是一些使用嵌套数组进行解构的例子 let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 let [ , , third] = ["foo

  • 详解Android中Handler的内部实现原理

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文<详解Android中Handler的使用方法>,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解. 概括来说,Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制.我们在使用Handler的时候与Message打交道最多,Message是Hanlder机制向开发人员暴露出来的相关类,可以通过Message类完成大部分操作Handler的功

  • ES6数组与对象的解构赋值详解

    本文实例讲述了ES6数组与对象的解构赋值.分享给大家供大家参考,具体如下: 数组的解构赋值 基本用法 ES6允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称之为解构(Destructuring) // 以前为变量赋值,只能直接指定值 var a = 1; var b = 2; var c = 3; // ES6允许写成这样 var [a,b,c] = [1,2,3]; 本质上,这种写法属于"模式匹配",只要等号两边的模式相同,左边的变量就会被赋予对应的值. 下面是一些使

  • 详解Python直接赋值,深拷贝和浅拷贝

    直接赋值: 对象的引用,也就是给对象起别名 浅拷贝: 拷贝父对象,但是不会拷贝对象的内部的子对象. 深拷贝: 拷贝父对象. 以及其内部的子对象 在之前的文章中,提到可变对象和不可变对象,接下来也是以这两者的区别进行展开 直接赋值 对于可变对象和不可变对象,将一个变量直接赋值给另外一个变量,两者 id 值一致,其实本质上是将变量量绑定到对象的过程. >>> a=1 >>> b=a >>> id(a) == id(b) True >>>

  • 详解JS ES6变量的解构赋值

    1.什么是解构? ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构.它在语法上比ES5所提供的更加简洁.紧凑.清晰.它不仅能减少你的代码量,还能从根本上改变你的编码方式. 2.数组解构 以前,为变量赋值,我们只能直接指定值,比如 let a = 1; let b = 2; let c = 3; 现在可以用数组解构的方式来进行赋值 let [a, b, c] = [1, 2, 3]; console.log(a, b, c); // 1, 2, 3 这是数组解构最基本类型

  • JavaScript解构赋值详解

    目录 概念 数组解构 声明分别赋值 解构默认值 交换变量值 解构函数返回的数组 忽略返回值(或跳过某一项) 赋值数组剩余值给一个变量 嵌套数组解构 字符串解构 对象解构 基础对象解构 赋值给新变量名 解构默认值 赋值给新对象名的同时提供默认值 同时使用数组和对象解构 不完全解构 赋值剩余值给一个对象 嵌套对象解构(可忽略解构) 注意事项 小心使用已声明变量进行解构 函数参数的解构赋值 解构的用途 交换变量的值 从函数返回多个值 提取JSON数据 总结 概念 ES6提供了更简洁的赋值模式,从数组和

随机推荐