在PHP中,变量以 $ 符号开头,后面跟着变量名。变量不需要显式声明类型,PHP会根据赋给变量的值自动推断变量的类型,这被称为 动态类型(Dynamically Typed)。
例如:
<?php
$var = 123; // 整数类型
$var = "Hello, world!"; // 字符串类型
?>
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 代码中定义一个变量时,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;
}
符号表是一张哈希表,里面存储了变量名和变量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 */
------
思考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,如下图所示。
PHP 中,当一个变量被赋值给另一个变量时,PHP 并不会立即复制该变量的内存内容。相反,它会让两个变量共享同一块内存,并在需要修改其中一个变量的内容时,才真正执行内存复制的操作。这个过程被称为“写时复制”。
主要依赖于refcount_gc > 1,共享,和延时复制;
1.写时复制的细节
● 引用计数:每个变量都有一个引用计数(refcount),当多个变量指向同一个值时,引用计数增加。只有当引用计数为 1 且变量被修改时,PHP 才会执行实际的内存复制。
● 延迟复制:写时复制是一种延迟复制技术,只有在数据被修改时才会触发真正的复制操作。这减少了内存分配和复制的开销,尤其是在大量数据传递和赋值的场景下。
2.写时复制的优化效果
写时复制的主要优点是节省内存和提高性能,特别是在以下场景下效果显著:
● 函数传递参数:当一个大数组或字符串作为参数传递给函数时,如果函数不修改这个参数,PHP 不会为这个数组或字符串创建新的副本。
● 赋值操作:当一个变量被赋值给另一个变量时,如果不对其进行修改,PHP 不会复制变量的值,而是让它们共享内存。
—— 评论区 ——