PHP底层原理之:变量及数据结构(上篇)
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的特点是,它在同一时间只能存储一种数据类型,这是为了节省内存。属性名 变量类型 解释 lval long 对应PHP的int类型 dval double 对应PHP的float类型 str struct 对应PHP的string类型 *ht HashTable 对应PHP的array类型 obj zend_object_value 对应PHP的object类型 - 上边五种数据类型对应PHP的五种数据类型,PHP一共有8中数据类型,剩下三种:
- null:设置zval->type = IS_NULL,就可以不必设置value的值。
- bool:设置zval->type = IS_BOOL,完后设置zval.value.lval = 1/0。
- 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';
此代码生成了三个结构体,同时全局符号表中,多了三条记录,如下图所示
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,如下图所示。
思考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,如下图所示。
6.写时复制cow(cow copy on wriet)
PHP 中,当一个变量被赋值给另一个变量时,PHP 并不会立即复制该变量的内存内容。相反,它会让两个变量共享同一块内存,并在需要修改其中一个变量的内容时,才真正执行内存复制的操作。这个过程被称为“写时复制”。
主要依赖于refcount_gc > 1,共享,和延时复制;
1.写时复制的细节
● 引用计数:每个变量都有一个引用计数(refcount),当多个变量指向同一个值时,引用计数增加。只有当引用计数为 1 且变量被修改时,PHP 才会执行实际的内存复制。
● 延迟复制:写时复制是一种延迟复制技术,只有在数据被修改时才会触发真正的复制操作。这减少了内存分配和复制的开销,尤其是在大量数据传递和赋值的场景下。
2.写时复制的优化效果
写时复制的主要优点是节省内存和提高性能,特别是在以下场景下效果显著:
● 函数传递参数:当一个大数组或字符串作为参数传递给函数时,如果函数不修改这个参数,PHP 不会为这个数组或字符串创建新的副本。
● 赋值操作:当一个变量被赋值给另一个变量时,如果不对其进行修改,PHP 不会复制变量的值,而是让它们共享内存。
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »