指尖上的记忆指尖上的记忆
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub
首页
  • 基础
  • Laravel框架
  • Symfony框架
  • 基础
  • Gin框架
  • 基础
  • Spring框架
  • 命令
  • Nginx
  • Ai
  • Deploy
  • Docker
  • K8s
  • Micro
  • RabbitMQ
  • Mysql
  • PostgreSsql
  • Redis
  • MongoDb
  • Html
  • Js
  • 前端
  • 后端
  • Git
  • 知识扫盲
  • Golang
🌟 gitHub
PHP之对象传递问题
本来以为对象作为参数传递或者赋值是引用传递的,但是不是这样的,其实是对象标识符(即对象句柄)传递,这个句柄允许访问实际的对象。但是有引用传递的效果。

示例:
class Test {
    public $value = 1;
}

function modify(Test $obj) {
    $obj->value = 99;
}

$a = new Test();
modify($a);

echo $a->value; // 输出 99,说明 modify 中的修改影响了原始对象



class Person {
    public string $name = 'Alice';
}

$a = new Person();
$b = $a;

$b->name = 'Bob';

echo $a->name; // 输出 Bob ✅



如果想创建一个新的独立对象,必须使用 clone:
$c = clone $a;
$c->name = 'Charlie';

echo $a->name; // 仍然是 Bob ✅
echo $c->name; // 是 Charlie ✅


官方文档说明: https://www.php.net/manual/en/language.oop5.references.php


对象句柄和&引用传递的区别:
对于 $b = $a 来说, $b 和 $a 是两个变量,分别有自己的内存地址,但是指向的内容是一样的, $b 如果再次被赋值为$c,那就和$a 没关系了
对于 $b = &$a 来说, $b就是$a的alias(别名), 它们是同一个变量的两个名字, b 如果再次被赋值为$c, 那么$a 也要变
通过zval的角度分析:

我们现在来深入底层探讨:

当执行 $a = new StdClass(); $b = $a; 时,$a 的 zval 到底是什么结构,$b 的 zval 和 $a 的有什么不同?

我们将分两步:

  1. 先理解 zval 是什么(PHP 内部结构)
  2. 具体讲 $a = new StdClass(); $b = $a; 时发生了什么

🧠 第一步:什么是 zval?

zval 是 PHP 用来存储变量的内部结构。

一个 zval 包括这些核心部分:

字段含义
value存储实际值(int、string、object 句柄等)
refcount引用计数(这个值被几个变量引用)
is_ref是否是显式引用(通过 & 赋值)
type类型信息,如 IS_STRING、IS_OBJECT 等

🧪 第二步:执行 $a = new StdClass(); 后发生了什么?

$a = new StdClass();

这一步会创建:

🔹 一个对象结构(heap 上):

对象实例(如地址 0xABC123):
  class_name => StdClass
  properties => []

🔹 一个 zval 给 $a:

zval_a:
  type => IS_OBJECT
  value => handle 指向 0xABC123
  refcount => 1
  is_ref => 0

变量 $a 指向这个 zval_a。


然后 $b = $a; 又做了什么?

这一步不是复制对象,也不是复制 zval,而是:

  • 复制 zval 的指针(zval_b 指向和 zval_a 同一块内存)
  • zval 的引用计数 refcount++

所以现在:

$a 和 $b 都指向同一个 zval:

zval_shared:
  type => IS_OBJECT
  value => handle → 0xABC123
  refcount => 2
  is_ref => 0

注意:$a 和 $b 是两个变量名,但它们指向同一个 zval(zval 本身在内存中只有一份)


✅ 修改对象属性时的行为

$b->x = 123;

你是在操作对象句柄(0xABC123)指向的内存,所以 $a->x 也变了。


❗但再举一个反例,看看不一样的地方:

$a = new StdClass();
$b = $a;
$b = new StdClass(); // 此时 b 不再指向原对象

现在 $b 会指向新的 zval:

$b → zval_b_new
  value => handle → 0xDEF456
  refcount => 1

$a → 原 zval_a
  value => handle → 0xABC123
  refcount => 1(原来是 2,现在 -1)

🔁 如果你用了引用 &:

$a = new StdClass();
$b = &$a;

此时会合并 $a 和 $b 的符号表变量名,让它们指向同一个 zval,并把 is_ref 设置为 1:

zval_ref:
  type => IS_OBJECT
  value => handle → 0xABC123
  refcount => 2
  is_ref => 1

现在,无论你对 $a 还是 $b 赋新值、改指向,都完全等价了(引用语义)。


🧪 如何验证这些行为?

可以用 PHP 的 debug_zval_dump():

$a = new StdClass();
$b = $a;

debug_zval_dump($a);

输出类似:

object(stdClass)#1 (0) refcount(2)

说明 $a 的 zval 被引用了 2 次($a 和 $b)。


✅ 总结

操作行为
$a = new StdClass();创建 zval,refcount = 1,is_ref = 0
$b = $a;指向同一 zval,refcount++
$b = &$a;真正引用,两个变量名绑定同一个 zval,is_ref = 1
修改对象属性改的是对象实例本身,两个变量看到的结果都变
重新赋新对象给 $b$b 指向新 zval,$a 不受影响

可以发现,refcount 都为2,但是 is_ref 一个是0 一个是1

官方文档说明: https://www.php.net/manual/zh/function.debug-zval-dump.php

关于写时复制:

我们现在来深入讲解:


🧠 PHP 的 Copy-On-Write(COW,写时复制)机制 + zval 管理图解


一、背景:PHP 的变量实现方式

PHP 每个变量都不是直接保存值,而是通过一个叫 zval 的结构来管理值和引用关系。

一个 zval 结构包含:

  • value: 变量的值(string/int/object/array...)
  • refcount: 有多少个变量共享这个 zval(引用计数)
  • is_ref: 是否为“引用模式”(即通过 & 建立 alias)

二、赋值行为图解

情况 1:普通赋值(非引用)

$a = 'hello';
$b = $a;

🔍 发生了什么:

+------+        +-----------------------+
| $a   | -----> | zval:                 |
|      |        | value: "hello"        |
| $b   | -----> | refcount: 2           |
|      |        | is_ref: 0             |
+------+        +-----------------------+
  • $a 和 $b 指向同一个 zval
  • refcount = 2
  • 但 is_ref = 0,表示它们不是绑定 alias
  • 改 $a = 'world' 会发生 写时复制(Copy-On-Write)

写时复制触发(COW)

$a = 'hello';
$b = $a;
$a = 'world';

🔍 发生了什么:

Step 1:
$a, $b --> "hello" (refcount = 2, is_ref = 0)

Step 2:
$a = 'world'; // 修改触发 Copy-On-Write:

    -> PHP 创建一个新的 zval 给 $a
    -> 原 zval 的 refcount--(变 1)
    -> 新 zval 的 refcount = 1

结果:

$a ---> "world" (refcount = 1)
$b ---> "hello" (refcount = 1)

👉 这样就节省了内存,直到你真的需要修改才复制。


情况 2:引用赋值(使用 &)

$a = 'hello';
$b = &$a;

🔍 发生了什么:

+------+        +------------------------+
| $a   | -----> | zval:                  |
| $b   | -----> | value: "hello"         |
|      |        | refcount: 2            |
|      |        | is_ref: 1   <--------- 绑定 alias
+------+        +------------------------+
  • $a 和 $b 完全绑定,是 同一个变量的两个名字
  • 改任意一方,另一方立即变
  • 赋值也影响彼此(不是写时复制)
$b = 'world'; // $a 也变成 'world'

三、对象赋值与 COW 的不同

$obj1 = new stdClass();
$obj2 = $obj1;

在对象赋值中:

  • 变量指向的是一个对象句柄(handle)
  • PHP 不会复制对象,默认行为就是“引用共享对象”
$obj2->x = 123; // $obj1->x 也变

但重要的是:

$obj2 = new stdClass(); // ❌ $obj1 保持原对象

对象赋值 不会触发 COW,因为对象内部不是简单值,而是指向堆上对象内存。


四、变量表、zval、内存图

PHP 的变量机制可视化如下:

Symbol Table (符号表)
+------+     +-----------+
| $a   | --> | zval A    |
| $b   | --> | zval A    |
+------+     +-----------+

当赋值或引用时:

  • 变量表指向同一个 zval
  • 修改时,根据 refcount 和 is_ref 判断是否需要 copy
  • refcount > 1 && is_ref == 0 → 会触发 COW
  • is_ref == 1 → 强制引用,绝不复制

五、最佳口诀记忆:

类型refcountis_ref是否复制
普通赋值✅ 增加❌ 否✅ 修改时复制
引用赋值(&)✅ 增加✅ 是❌ 不复制
对象赋值✅ 共享❌ 否❌ 不复制,操作引用

🧪 推荐调试方法

使用 debug_zval_dump($var) 查看:

$a = 'abc';
$b = $a;

debug_zval_dump($a);
/*
string(3) "abc"
refcount(2)
is_ref=0
*/

再用 $b = &$a; 试试看区别。