侧边栏壁纸
博主昵称
流苏小筑

步伐虽小,密而不止

PHP底层原理之:变量及数据结构(上篇)

2023年02月03日 53阅读 0评论 0点赞

PHP底层原理之:变量及数据结构(上篇)

PHP中,变量和数据结构是程序的基础组成部分。了解它们的底层原理,可以帮助我们更高效地编写代码,提升性能,避免常见的坑。

1. PHP变量的基本概念

PHP中,变量以 $ 符号开头,后面跟着变量名。变量不需要显式声明类型,PHP会根据赋给变量的值自动推断变量的类型,这被称为 动态类型(Dynamically Typed)。

例如:

<?php
$var = 123;  // 整数类型
$var = "Hello, world!";  // 字符串类型
?>

2.底层实现:

PHP变量的底层实现是通过 ZVAL(Zend Value) 来存储的。每个PHP变量都由一个ZVAL结构体表示,ZVAL中保存了变量的值及其类型信息。
ZVAL结构是PHP中所有变量的统一存储格式,允许PHP引擎快速访问和管理变量。
PHP源码文件:Zend/zend.h

/*
 * PHP变量的核心结构体
 */
struct _zval_struct {
    zvalue_value value;        // 1.存储变量的值(是一个联合体)
    zend_uchar type;           // 2.存储变量的类型
                              // IS_LONG:对应PHP---- Int
                              // IS_DOUBLE:对应PHP---- float
                              // IS_STRING:对应PHP---- string
                              // IS_ARRAY:对应PHP---- array
                              // IS_OBJECT:对应PHP---- object
                              // IS_NULL:对应PHP---- null
                              // IS_BOOL:对应PHP---- bool
                              // IS_RESOUCE:对应PHP---- resouce
    zend_uint refcount_gc; // 3.引用计数器,用于管理内存
    zend_uchar is_ref_gc;  // 4.用于标记这个变量是否是一个引用
};

/**
 * 联合体:存储不同类型变量的值
 */
typedef union _zvalue_value {
    long lval;                    // 1.存储 PHP:int
    double dval;                // 2.存储 PHP:float
    struct {                    // 3.存储 PHP:string
        char *val;                    // 3.1指向实际字符串内容
        int len;                    // 3.2字符串长度
    } str;
    HashTable *ht;                // 4.存储 PHP:array (本质是一个hash表,这里指向这个hash表)
    zend_object_value obj;        // 5.存储 PHP:obj (zend_object_value是一个结构体,代表php的对象)
    zend_ast *ast;
} zvalue_value;
  • struct _zval_struct
    _zval_struct :是 PHP 变量的核心结构体,它结合了值、类型信息以及引用计数,来管理和操作 PHP 中的变量。

    属性名含义默认值
    value是一个联合体,用来存储变量的实际值
    type一个字节变量,用来存储变量的具体类型。
    refcount__gc引用计数器,用来统计变量的引用次数,以便在没有引用的时候进行释放内存,refcount__gc统计了有多少个地方在使用这个变量,如果这个计数变为0,则会释放这个变量的内存。1
    is_ref_gc用于标记变量是否是一个引用。PHP支持通过& 符来创建一个变量引用。0
  • ypedef union _zvalue_value
    _zvalue_value :这个联合体(union),就是上边_zval_struct 的具体实现,可以存储不同类型的变量值(int,float,string,array,object)。union的特点是,它在同一时间只能存储一种数据类型,这是为了节省内存。

    属性名变量类型解释
    lvallong对应PHP的int类型
    dvaldouble对应PHP的float类型
    strstruct对应PHP的string类型
    *htHashTable对应PHP的array类型
    objzend_object_value对应PHP的object类型
  • 上边五种数据类型对应PHP的五种数据类型,PHP一共有8中数据类型,剩下三种:
  1. null:设置zval->type = IS_NULL,就可以不必设置value的值。
  2. bool:设置zval->type = IS_BOOL,完后设置zval.value.lval = 1/0。
  3. resouce:设置zval->type = IS_RESOUCE,完后设置zval.value.lval = 资源打开接口的编号

3.变量的工作机制

当你在 PHP 代码中定义一个变量时,Zend 引擎在后台会创建一个 _zval_struct 结构体来存储该变量。根据变量的类型,值会被存储在 zvalue_value 的不同成员中(例如,如果是整数,会存储在 lval 中;如果是字符串,会存储在 str 中)。type 字段则指示当前变量的类型。

$a = "hello world";

/**
 * zend创建一个结构体
 */
_zval_struct: {
    value : {
        char:"hello world";
        len:11;
    };
    type:IS_STRING;
    refcount_gc:1;
    
}

4.变量花名册 (也叫 变量表 或 符号表)

符号表是一张哈希表,里面存储了变量名和变量zval结构体的地址的映射。
$a -> 0x123456($a对应zval结构体地址)
例如:

<?php
$a = 3;
$b = 3.1415926;
$c = 'hello world';

此代码生成了三个结构体,同时全局符号表中,多了三条记录,如下图所示
m4behg0o.png

PHP源码文件:Zend/zend_globals.h

struct _zend_executor_globals {
    ...
    ...
    HashTable *active_symbol_table; // 活动符号表
    HashTable symbol_table;            // 全局符号表

    HashTable included_files;    /* files already included */

------

5.变量的传值赋值

思考1:下面代码是否会产生两个结构体?

<?php
$a = 3;
$b = $a;

不会,因为是 $b = $a, $b也会指向初始化$a时生成的结构体,两个变量共享一个结构体,并且引用计数器加1,refcount_gc = 2,如下图所示。
m4bf47o1.png

思考2:既然两个变量共享一个结构体,那么修改其中一个是否会影响另一个变量。

<?php

$a = 3;
$b = $a;

$b = 5;
echo $a, $b; // 修改$b,此时$a等于多少?

不会,当修改$b = 5时,发现refcount_gc > 1,也就是说明除了$b,还有其他变量在和$b共享这个结构体,所以会复制一个$a的副本,给$b使用,并且将原结构体的refcount_gc的值减1,新结构体的refcount_gc = 1,value的值改为5,如下图所示。
m4bf8e7l.png


6.写时复制cow(cow copy on wriet)

PHP 中,当一个变量被赋值给另一个变量时,PHP 并不会立即复制该变量的内存内容。相反,它会让两个变量共享同一块内存,并在需要修改其中一个变量的内容时,才真正执行内存复制的操作。这个过程被称为“写时复制”。
主要依赖于refcount_gc > 1,共享,和延时复制;

1.写时复制的细节
● 引用计数:每个变量都有一个引用计数(refcount),当多个变量指向同一个值时,引用计数增加。只有当引用计数为 1 且变量被修改时,PHP 才会执行实际的内存复制。
● 延迟复制:写时复制是一种延迟复制技术,只有在数据被修改时才会触发真正的复制操作。这减少了内存分配和复制的开销,尤其是在大量数据传递和赋值的场景下。

2.写时复制的优化效果
写时复制的主要优点是节省内存和提高性能,特别是在以下场景下效果显著:
● 函数传递参数:当一个大数组或字符串作为参数传递给函数时,如果函数不修改这个参数,PHP 不会为这个数组或字符串创建新的副本。
● 赋值操作:当一个变量被赋值给另一个变量时,如果不对其进行修改,PHP 不会复制变量的值,而是让它们共享内存。


0

—— 评论区 ——

昵称
邮箱
网址
取消