首页 > 技术文章 > php的引用类型底层解析

linjingyg 2021-12-11 12:50 原文

  我们来先看一段代码

  

  $a="string";

  $b=&$a;

  echo $a;

  echo $b;

  $b="hello";

  echo $b;

  echo $a;

  unset($b);

  echo $b;

  echo $a;

  ?>

  输出结果为

  string

  string

  hello

  hello

  (空)

  hello

  为什么会输出这样的结果呢?我们来分析一下

  首先我们看一下引用类型的结构

  struct _zend_reference {

  zend_refcounted_h gc;

  zval val;

  };

  我们可以看到,引用类型是一个变量zval和一个zend_refcounted_h组成

  先看第一段的

  $a="string";

  $b=&$a;

  echo $a;

  echo $b;

  执行过程

  (gdb) p *z

  $1={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=10 '

  ', type_flags=4 '\004', const_flags=0 '\000', reserved=0 '\000'}, type_info=1034}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  //我们可以看到$a的u1的type为10,所以说明$a已经是引用类型了,对应的内存地址为0x7ffff5a020a8

  (gdb) p *$1.value.ref

  $2={gc={refcount=2, u={v={type=10 '

  ', flags=0 '\000', gc_info=0}, type_info=10}}, val={value={lval=17733632, dval=8.7615783471909966e-317,

  counted=0x10e9800, str=0x10e9800, arr=0x10e9800, obj=0x10e9800, res=0x10e9800, ref=0x10e9800, ast=0x10e9800, zv=0x10e9800, ptr=0x10e9800, ce=0x10e9800,

  func=0x10e9800, ww={w1=17733632, w2=0}}, u1={v={type=6 '\006', type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=6}, u2={

  next=28776, cache_slot=28776, lineno=28776, num_args=28776, fe_pos=28776, fe_iter_idx=28776, access_flags=28776, property_guard=28776, extra=28776}}}

  //我们可以看到在$a的引用内部 是由gc和val组成,而且val就是一个zval,对应的type是6,字符串类型

  (gdb) p *$1.value.ref.val.value.str

  $3={gc={refcount=1, u={v={type=6 '\006', flags=7 '\a', gc_info=0}, type_info=1798}}, h=9223378990886268924, len=6, val="s"}

  (gdb) p *$1.value.ref.val.value.str.val@6

  $4="string"

  //对应的打印出ref中的str类型的字符串

  (gdb) p z

  $5=(zval *) 0x7ffff5a14090

  (gdb) p *z

  $6={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=10 '

  ', type_flags=4 '\004', const_flags=0 '\000', reserved=0 '\000'}, type_info=1034}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  //我们可以看到$b的u1的type为10,所以说明$b已经是引用类型了,对应的内存地址为0x7ffff5a020a8 和$a共用一个地址

  (gdb) p $6.value.ref

  $7=(zend_reference *) 0x7ffff5a020a8

  (gdb) p *$6.value.ref

  $8={gc={refcount=2, u={v={type=10 '

  ', flags=0 '\000', gc_info=0}, type_info=10}}, val={value={lval=17733632, dval=8.7615783471909966e-317,

  counted=0x10e9800, str=0x10e9800, arr=0x10e9800, obj=0x10e9800, res=0x10e9800, ref=0x10e9800, ast=0x10e9800, zv=0x10e9800, ptr=0x10e9800, ce=0x10e9800,

  func=0x10e9800, ww={w1=17733632, w2=0}}, u1={v={type=6 '\006', type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=6}, u2={

  next=28776, cache_slot=28776, lineno=28776, num_args=28776, fe_pos=28776, fe_iter_idx=28776, access_flags=28776, property_guard=28776, extra=28776}}}

  //$b中的ref也是由gc和zval组成,而且对应的zval中的u1的type为6,是字符串类型

  (gdb) p *$6.value.ref.val.value.str

  $9={gc={refcount=1, u={v={type=6 '\006', flags=7 '\a', gc_info=0}, type_info=1798}}, h=9223378990886268924, len=6, val="s"}

  (gdb) p *$6.value.ref.val.value.str.val@6

  $10="string"

  //打印出字符串

  接下来我们看看

  $b="hello";

  echo $b;

  echo $a;

  (gdb) p *z

  $11={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=10 '

  ', type_flags=4 '\004', const_flags=0 '\000', reserved=0 '\000'}, type_info=1034}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  //我们可以看到$b的u1的type为10,所以说明$b已经是引用类型了,对应的内存地址为0x7ffff5a020a8

  (gdb) p *$11.value.ref

  $12={gc={refcount=2, u={v={type=10 '

  ', flags=0 '\000', gc_info=0}, type_info=10}}, val={value={lval=140737314679872, dval=6.9533472271273708e-310,

  counted=0x7ffff5a5ec40, str=0x7ffff5a5ec40, arr=0x7ffff5a5ec40, obj=0x7ffff5a5ec40, res=0x7ffff5a5ec40, ref=0x7ffff5a5ec40, ast=0x7ffff5a5ec40,

  zv=0x7ffff5a5ec40, ptr=0x7ffff5a5ec40, ce=0x7ffff5a5ec40, func=0x7ffff5a5ec40, ww={w1=4121291840, w2=32767}}, u1={v={type=6 '\006',

  type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=6}, u2={next=28776, cache_slot=28776, lineno=28776, num_args=28776,

  fe_pos=28776, fe_iter_idx=28776, access_flags=28776, property_guard=28776, extra=28776}}}

  //$b中的ref是由gc和zval组成,而且对应的zval中的u1的type为6,是字符串类型

  (gdb) p *$11.value.ref.val.value.str

  $13={gc={refcount=0, u={v={type=6 '\006', flags=2 '\002', gc_info=0}, type_info=518}}, h=9223372247569412249, len=5, val="h"}

  (gdb) p *$11.value.ref.val.value.str.val@5

  $14="hello"

  //打印出对应的字符串

  (gdb) p *z

  $15={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=10 '

  ', type_flags=4 '\004', const_flags=0 '\000', reserved=0 '\000'}, type_info=1034}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  //我们可以看到$a的u1的type为10,所以说明$a已经是引用类型了,对应的内存地址为0x7ffff5a020a8 和b一样

  (gdb) p *$15.value.ref

  $16={gc={refcount=2, u={v={type=10 '

  ', flags=0 '\000', gc_info=0}, type_info=10}}, val={value={lval=140737314679872, dval=6.9533472271273708e-310,

  counted=0x7ffff5a5ec40, str=0x7ffff5a5ec40, arr=0x7ffff5a5ec40, obj=0x7ffff5a5ec40, res=0x7ffff5a5ec40, ref=0x7ffff5a5ec40, ast=0x7ffff5a5ec40,

  zv=0x7ffff5a5ec40, ptr=0x7ffff5a5ec40, ce=0x7ffff5a5ec40, func=0x7ffff5a5ec40, ww={w1=4121291840, w2=32767}}, u1={v={type=6 '\006',

  type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=6}, u2={next=28776, cache_slot=28776, lineno=28776, num_args=28776,

  fe_pos=28776, fe_iter_idx=28776, access_flags=28776, property_guard=28776, extra=28776}}}

  //$a中的ref是由gc和zval组成,而且对应的zval中的u1的type为6,是字符串类型

  (gdb) p *$15.value.ref.val.value.str

  $17={gc={refcount=0, u={v={type=6 '\006', flags=2 '\002', gc_info=0}, type_info=518}}, h=9223372247569412249, len=5, val="h"}

  (gdb) p *$15.value.ref.val.value.str.val@5

  $18="hello"

  打印字符串

  接下来我们再来看看

  unset(b);echo b;

  echo $a;

  (gdb) p *z

  $1={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=0 '\000', type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=0}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  //大家其实可以看到 在unset($b)的操作过程中,仅仅是把b中的u1的type改为了0,为null类型,其余的地址等信息都未改变,所以对应的$a是不会有任何改变的

  所以后面在打印$a的过程中,一切都是正常的,以下为$a的打印过程

  (gdb) p *z

  $2={value={lval=140737314300072, dval=6.9533472083627576e-310, counted=0x7ffff5a020a8, str=0x7ffff5a020a8, arr=0x7ffff5a020a8, obj=0x7ffff5a020a8,

  res=0x7ffff5a020a8, ref=0x7ffff5a020a8, ast=0x7ffff5a020a8, zv=0x7ffff5a020a8, ptr=0x7ffff5a020a8, ce=0x7ffff5a020a8, func=0x7ffff5a020a8, ww={w1=4120912040,

  w2=32767}}, u1={v={type=10 '

  ', type_flags=4 '\004', const_flags=0 '\000', reserved=0 '\000'}, type_info=1034}, u2={next=0, cache_slot=0, lineno=0,

  num_args=0, fe_pos=0, fe_iter_idx=0, access_flags=0, property_guard=0, extra=0}}

  (gdb) p *$2.value.ref

  $3={gc={refcount=1, u={v={type=10 '

  ', flags=0 '\000', gc_info=0}, type_info=10}}, val={value={lval=140737314679872, dval=6.9533472271273708e-310,

  counted=0x7ffff5a5ec40, str=0x7ffff5a5ec40, arr=0x7ffff5a5ec40, obj=0x7ffff5a5ec40, res=0x7ffff5a5ec40, ref=0x7ffff5a5ec40, ast=0x7ffff5a5ec40,

  zv=0x7ffff5a5ec40, ptr=0x7ffff5a5ec40, ce=0x7ffff5a5ec40, func=0x7ffff5a5ec40, ww={w1=4121291840, w2=32767}}, u1={v={type=6 '\006',

  type_flags=0 '\000', const_flags=0 '\000', reserved=0 '\000'}, type_info=6}, u2={next=28776, cache_slot=28776, lineno=28776, num_args=28776,

  fe_pos=28776, fe_iter_idx=28776, access_flags=28776, property_guard=28776, extra=28776}}}

  (gdb) p *$2.value.ref.val.value.str

  $4={gc={refcount=0, u={v={type=6 '\006', flags=2 '\002', gc_info=0}, type_info=518}}, h=9223372247569412249, len=5, val="h"}

  (gdb) p *$2.value.ref.val.value.str.val@5

  $5="hello"

推荐阅读