php的引用类型底层解析

2020-09-07 15:30:29 浏览数 (1)

我们来先看一段代码

代码语言:javascript复制
<?php
$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

为什么会输出这样的结果呢?我们来分析一下 首先我们看一下引用类型的结构

代码语言:javascript复制
struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
};

我们可以看到,引用类型是一个变量zval和一个zend_refcounted_h组成 先看第一段的 a = "string";

代码语言:javascript复制
(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 'n', type_flags = 4 '04', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', flags = 0 '00', 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 '06', type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 '06', 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 'n', type_flags = 4 '04', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', flags = 0 '00', 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 '06', type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 '06', 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;

代码语言:javascript复制
(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 'n', type_flags = 4 '04', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', flags = 0 '00', 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 '06',
        type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 '06', flags = 2 '02', 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 'n', type_flags = 4 '04', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', flags = 0 '00', 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 '06',
        type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 '06', flags = 2 '02', 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 $a;

代码语言:javascript复制
(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 '00', type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', type_flags = 4 '04', const_flags = 0 '00', reserved = 0 '00'}, 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 'n', flags = 0 '00', 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 '06',
        type_flags = 0 '00', const_flags = 0 '00', reserved = 0 '00'}, 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 '06', flags = 2 '02', gc_info = 0}, type_info = 518}}, h = 9223372247569412249, len = 5, val = "h"}
(gdb) p *$2.value.ref.val.value.str.val@5
$5 = "hello"

0 人点赞