php 7.4.21中闭包底层是zend_closure结构体,继承zend_object,含func(opcode引用)、used_vars(use变量快照数组)、this_ptr等字段;创建时在编译期末调用zend_create_closure深拷贝变量,调用时将used_vars挂载为局部符号表执行。

PHP 7.4.21 中闭包(Closure)的底层实现,核心在于 Zend 引擎对 zend_closure 结构体的封装与运行时机制。它不是语法糖,而是被内核作为一类特殊对象(zend_object 子类)严格管理的实体。理解其源码实现,需聚焦于三个层面:结构定义、创建过程、变量捕获逻辑。
zend_closure 结构体是闭包的内存骨架
在 PHP 源码中(Zend/zend_closures.h),zend_closure 是继承自 zend_object 的结构:
typedef struct _zend_closure {
zend_object std;
zend_function func;
zend_object *this_ptr;
zend_class_entry *called_scope;
zval *func_ptr;
zval *this_ptr_ptr;
zval *used_vars; // 关键:存储 use 捕获的变量(zval 数组)
} zend_closure;
-
func:保存编译后的函数字节码(opcodes)、参数信息、静态变量等,和普通函数一样走zend_op_array流程; -
used_vars:指向一个zval*数组,每个元素对应use($a, $b, &$c)中的一个被捕获变量 —— 这是“封闭”的物理载体; -
this_ptr/called_scope:支持绑定对象上下文(如bindTo()),使闭包可访问私有成员; - 闭包对象本身不直接存函数体,而是通过
func引用共享的zend_op_array,多个相同定义的闭包可复用同一份 opcodes。
闭包对象的创建发生在编译期末尾
当 Zend 编译器遇到 function() use (...) { ... } 时:
- 先解析函数体,生成
zend_op_array(存于CG(function_table)); - 再在编译完成阶段(
zend_do_end_function_declaration),调用zend_create_closure(); - 该函数分配
zend_closure对象,初始化std,并为used_vars分配内存; - 对每个
use变量,执行ZVAL_COPY(&used_vars[i], &original_zval)(值传递)或ZVAL_MAKE_REF()+Z_ADDREF_P()(引用传递),确保捕获的是定义时刻的快照。
use 变量的捕获是深拷贝 + 引用标记,非动态链接
关键点在于:闭包不持有对外部栈变量的指针,而是复制值(或引用计数)到自己的 used_vars 区域。
- 值捕获(
use($x)):调用ZVAL_COPY(),得到独立副本,后续外部$x改变不影响闭包; - 引用捕获(
use(&$x)):调用ZVAL_COPY()后再Z_SET_ISREF(),让闭包内zval和外部共享同一zval容器(refcount > 1); - 所有捕获动作在
zend_create_closure()中一次性完成,无运行时符号查找 —— 这也是为何传统闭包比箭头函数慢:箭头函数在编译期就将变量地址硬编码进 opcode,跳过used_vars查表。
调用时的执行路径极简
调用 $closure() 实际触发 zend_closure_call()(Zend/zend_closures.c):
- 将
used_vars数组挂载为当前执行帧(execute_data->symbol_table)的局部变量; - 设置
execute_data->This和scope; - 跳转至
func.op_array执行字节码; - 返回前清理
used_vars的局部符号表映射(但不销毁 zval,因由 GC 管理)。
简单说:闭包 = 函数字节码 + 一份打包好的变量快照 + 上下文绑定能力。它在 7.4.21 中仍未支持序列化(serialize() 抛出异常),因其 used_vars 可能含资源、对象或绑定的 $this,无法安全跨进程重建。
本质上,PHP 闭包是 Zend 引擎为函数式编程提供的“作用域快照容器”,所有魔法都建立在编译期固化变量关系、运行时注入符号表这一机制之上。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











