php教程

PHP弱变量类型如何实现?PHP弱类型变量的只是介绍

php教程 51源码 2023-12-29 人阅读

PHP是一种弱类型、动态的脚本语言。在声明变量时,无需指定数据类型。PHP是通过一个zval的结构体实现的,其基本原理如下:

PHP通过一个结构体_zval_struct来保存变量的信息,包括值和类型。

_zval_struct中的type字段表示变量的当前类型。常见的类型有IS_NULL、IS_LONG、IS_STRING、IS_ARRAY、IS_OBJECT等。

_zval_struct中的value字段是一个联合体(union),根据类型的不同,取对应的值。

联合体的特点是允许在相同的内存位置存储不同的数据类型,但同一时刻只有一个成员带有有效值,可以节省内存占用。

每次修改变量的类型时,只需修改type字段,并将对应的值赋给value联合体即可。

PHP使用引用计数来进行基本的垃圾回收。每个变量都有一个refcount__gc字段,表示变量的引用数目。

引用计数的回收算法称为Reference Counting,通过计数器的增减判断是否需要回收内存。

如果一个变量的引用数为0,就可以释放其占用的内存。如果引用数大于0,就需要进一步判断是否成为垃圾。

PHP的垃圾回收机制会将可能成为垃圾的变量收集起来,并通过模拟删除测试来判断是否需要回收内存。

为何是将泄漏控制在某个阈值以下?

因为这类操作无法避免,所以泄漏仍旧会存在;

添加了buffer待删除检测区后,可以一定程度上减少泄漏的持续增长,这个buffer的默认值是10000个变量,等凑满了才会进行处理,所以,这个机制会带来两个问题:

1、达到足够量级才会处理;

2、每次可处理的量级有上限;

因此,仍旧会有泄漏产生,但只要达到阈值,就会被处理掉:

但,如果有泄漏产生,那么内存的占用会是个折线图,忽高忽低,虽有泄漏,但可控;

而,旧版的垃圾回收机制,会导致泄漏是个线性增长的斜线图,泄漏没有上限,不可控。

php7版本以后的一些变化

其实从上面我们也可以看到,这类垃圾只有复杂类型才会出现,若变量是int类型,是不会出现引用自身的,所以简单类型其实不需要做啥计数;

因此PHP7开始,对于在zval的value字段中能保存下的值, 比如IS_LONG、IS_DOUBLE等简单类型,不再进行引用计数, 而是在拷贝时直接赋值, 这样就省掉了大量的引用计数相关的操作;

同样,对于那种根本没有值的类型, 比如,IS_NULL IS_FALSE IS_TRUE, 也不再引用计数了;

同时,PHP7给变量添加了type_flag,只有属于IS_TYPE_COLLECTABLE的变量才会被GC收集,比如 array,object等复杂类型。

关于PHP的 copy on write(写时复制) 和 change on write(写时改变)

再看一遍最开始贴的c源码中的 zval 结构体,还有个 is_ref__gc 字段没讲它的用途,这涉及PHP的 change on write 机制;

is_ref__gc 是一个标志位,表示PHP中的一个变量是否是 & 引用类型;

先来看一段代码:

$var = "var_str";

$var_dup = $var;

$var = 1;代码执行后,$var_dup 的值还是 var_str, 这就是通过 copy on write 机制实现的。

PHP在修改一个变量以前,会先查看这个变量的refcount,如果refcount大于1,PHP就会执行分离;

对于上面的代码,当执行到第三行时,由于 $var 指向的zval的refcount大于1,此时会复制一个新的zval出来,将原zval的refcount减1,并修改symbol_table(全局符号表,存储了所有的变量符号);

注:PHP通过此表存储变量符号,且每个复杂类型如数组都有自己的符号表,因此 $a和$a[0] 虽然是两个符号,但 $a 存在全局符号表,$a[0] 则存在数组本身的符号表

如果同时执行如下调试代码:

debug_zval_dump($var);

debug_zval_dump($var_dup);会看到这俩变量的 refcount 都是2(debug_zval_dump执行时也会导致 refcount+1),也就是这俩变量已经做了分离。

注:本段测试代码需使用5版本,7版本以后由于计数方式变化,int等简单类型不再计数

因为PHP中,执行变量复制的时候 ,PHP内部并不是真正的复制,而是采用指向相同的结构来节约开销,类似 shallow copy(浅拷贝),当源变量发生变化时,再执行深拷贝。

同理,如果一个变量是引用类型,判断方式就会发生变化,参考如下代码:

$var = "var_str";
$var_ref = &$var;
$var_ref = 1;

这段代码结束以后,$var 也会被间接的修改为1,这个过程唤作 change on write(写时改变)。

当上面代码的第二行执行后,除了 $var 变量的 refcount+1变为2 外,其 is_ref__gc 也会被设置为 1,代表当前变量被引用了;

执行到第三行时,PHP会检查is_ref字段,如果为 1,则不执行分离,而是直接将 源变量$var 的值修改为 1。

而如果将上面的代码改为:

$var = "var_str";
$var_dup = $var;
$var_ref = &$var;

同时存在 copy on write 和 change on write,执行逻辑如下:

第二行执行时,和前面讲的一样,$var_dup 和 $var 指向相同的zval, refcount为2;

第三行执行时,PHP发现refcount大于1,则先执行分离操作, 将 $var_dup 分离出去,再将 $var和$var_ref 做 change on write 关联,也就是,refcount=2, is_ref=1;

下面来观察实际输出结果,代码改为如下:

$var = "var_str";
$var_dup = $var;
debug_zval_dump($var_dup);
$var_ref = &$var;
debug_zval_dump($var_dup);
debug_zval_dump($var);在PHP5版本中,输出结果如下:
string(7) "var_str" refcount(3)
string(7) "var_str" refcount(2)

string(7) "var_str" refcount(1)你会看到 debug_zval_dump($var_dup) 的执行结果,第一次输出refcount(3),第二次为 refcount(2), 说明此时$var_dup是个单独的变量,已从$var中分离出去;

那为什么 debug_zval_dump($var) 的执行结果是 refcount(1) 呢?

不是说 debug_zval_dump 执行时会导致变量自身的 refcount+1 吗,不应该输出 refcount(3) 吗?

PHP弱变量类型如何实现?PHP弱类型变量的只是介绍-第1张图片-文煞网站目录网

我们做个测试,调整下代码顺序,先执行引用,如下:


$var = "var_str";
$var_ref = &$var;
$var_dup = $var;
debug_zval_dump($var_dup);
debug_zval_dump($var);此时的输出结果为:
string(7) "var_str" refcount(2)
string(7) "var_str" refcount(1)


这就说明 $var_dup 在赋值时直接进行了分离,因为前一行的 $var_ref = &$var 已经让 $var 的 refcount=2, is_ref=1 了,同理,debug_zval_dump 输出$var时,会直接执行分离操作,由于是在debug_zval_dump内部分离的,输出时自然就只有被分离的变量自己,也就是 refcount(1) 了。

再次强调:

以上测试代码需使用5版本,7版本以后由于计数方式变化,都会输出(1)PHP5中的这种设计结构,会带来一个经典的性能问题,参考如下代码:


$array = range(1, 100000);
function dummy($array) {
//do something..here
}$b = &$array; //假设不小心把这个Array引用给了一个变量
$i = 0;
$start = microtime(true);
while($i++ < 100) {
dummy($array);
}printf("Used %ss\n", microtime(true) - $start);


由于5版本的设计问题,上面循环体中的代码,在dummy函数中调用时,其refcount>1, is_ref=1,会命中 Change On Write 逻辑,导致每循环一次,就会执行一遍$array的复制;

所以在PHP的7版本之后,修改 zval 结构的同时,增加了一个专门的引用类型 IS_REFERNCE 用来表示&这种引用关系;对于如下代码:

$var = "var_str";
$var_ref = &$var;
$var_dup = $var;

其执行逻辑变为如下情况:

1、第二行执行时,发现产生了引用,就会创建一个 IS_REFERNCE 类型的结构,我们取个代号叫 zend_ref;

2、这个 zend_ref 会指向第一行代码中 $var 变量的 zval,同时将 $var、$var_ref 的指向修改为自身,并修改自身的 refcount=2,也就是 $var、$var_ref 同时指向 zend_ref 这个 IS_REFERNCE 类型;

3、第三行执行时,发现 $var 是个IS_REFERNCE类型,就会越过$var,将$var_dup指向 zend_ref 后面真实的 zval,同时对 zval 执行refcount+1,并不会产生复制;

代码简单示意:

// $var -> zend_string(refcount=1)
$var = "var_str"; 
// [$var, $var_ref] -> zend_reference(refcount=2) -> zend_string(refcount=1)
$var_ref = &$var;
// [$var, $var_ref] -> zend_reference(refcount=2) -> zend_string(refcount=2)
// $var_dup -> zend_string(refcount=2)
$var_dup = $var;


这样就解决了上面提到的 经典性能问题


关于字段名的补充,在5.3版本以前,refcount字段称为refcount,而在5.3及后续版本中,为了对付循环引用计数,引入了新的垃圾回收算法,并将其改为refcount__gc字段。


垃圾产生的原理很简单,就是无限循环。当一个变量将引用指向自己时,会导致无限循环,从而产生垃圾。例如,当一个数组中的某个子元素指向数组本身时,就会导致内存泄漏。


垃圾回收有两种情况:如果一个变量的引用数减少后为0,说明该变量可以被释放,属于优质垃圾;而如果引用数减少后仍大于0,说明该变量仍可能成为垃圾,需要进行进一步操作。垃圾回收机制会将可能成为垃圾的变量收集起来,并放入一个buffer链表,通过模拟删除测试来判断是否需要回收内存。


以上就是关于PHP弱类型变量实现原理和垃圾回收的基本原理的总结。希望对您有所帮助!



$var = 1;
$var = "variable";
$var = 1.00;
$var = array();
$var = new Object();


变量在运行时是可以改变的,并且在使用之前无需声明变量类型。那么问题一,Zend引擎是如何用C语言实现这种弱类型的呢?


实际上,在PHP中声明的变量在Zend引擎(ZE)中都是用结构体zval来保存的。让我们先来看一下Zend/zend.h文件中zval的定义:


typedef struct _zval_struct zval;
struct _zval_struct {
  /* Variable information */
  zvalue_value value; /* value */
  zend_uint refcount__gc;
  zend_uchar type; /* active type */
  zend_uchar is_ref__gc;
};
typedef union _zvalue_value {
  long lval; /* long value */
  double dval; /* double value */
  struct {
    char *val;
    int len;
  } str;
  HashTable *ht; /* hash table value */
  zend_object_value obj;
} zvalue_value;


从上述代码中可以看出,_zvalue_value是真正保存数据的关键部分。通过共用体的方式,实现了弱类型变量的声明。


问题二,Zend引擎是如何判别和存储PHP中的多种数据类型的呢?


_zval_struct.type中存储着变量的真正类型,根据type的值选择如何获取zvalue_value的值。


以下是type值的列表(Zend/zend.h):


#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9


让我们来看一个简单的例子:


$a = 1;
//此时zval.type = IS_LONG,zval.value取lval的值。
$a = array();
//此时zval.type = IS_ARRAY,zval.value取ht的值。


其中最复杂的类型是“资源类型”,它经常在开发第三方扩展中被使用。在PHP中,任何不属于PHP内置变量类型的变量都被视为资源来进行保存,比如数据库句柄、打开的文件句柄、打开的socket句柄等。


资源类型需要使用Zend引擎提供的API函数进行注册,并且资源变量的声明和使用将在单独的篇目中进行详细介绍。正是由于Zend引擎的处理方式,使得PHP实现了弱类型。对于Zend引擎来说,它始终面对的是同一种类型——zval。

版权声明:文章搜集于网络,如有侵权请联系本站,转载请说明出处:https://www.51yma.cn/jiaocheng/php/1369.html
文章来源:文煞PHP笔记网-http://old.wensha.info/post/1281.html