• 企业400电话
  • 网络优化推广
  • AI电话机器人
  • 呼叫中心
  • 全 部 栏 目

    网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    PHP赋值的内部是如何跑的详解
    POST TIME:2021-10-18 04:45

    前言

    在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' 判断过程如下:

    简单变量

    先引用赋值后普通赋值

    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。

    总结

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

    您可能感兴趣的文章:
    • php 传值赋值与引用赋值的区别
    • PHP读取txt文件的内容并赋值给数组的代码
    • 详解PHP数组赋值方法
    • php foreach 使用(与运算符)引用赋值要注意的问题
    • php中给js数组赋值方法
    • PHP5 的对象赋值机制介绍
    • PHP变量赋值、代入给JavaScript中的变量
    • php数组操作之键名比较与差集、交集赋值的方法
    • PHP基础陷阱题(变量赋值)
    上一篇:PHP5.5新特性之yield理解与用法实例分析
    下一篇:php成功操作redis cluster集群的实例教程
  • 相关文章
  • 

    关于我们 | 付款方式 | 荣誉资质 | 业务提交 | 代理合作


    © 2016-2020 巨人网络通讯

    时间:9:00-21:00 (节假日不休)

    地址:江苏信息产业基地11号楼四层

    《增值电信业务经营许可证》 苏B2-20120278

    X

    截屏,微信识别二维码

    微信号:veteran88

    (点击微信号复制,添加好友)

     打开微信