
zval 是什么,为什么不能直接用 C 变量
PHP 扩展里操作变量,本质是操作 zval 结构体。它不是 C 的 int 或 char*,而是带类型、引用计数、GC 标记的复合体。直接读写 zval->value.lval 会崩溃或内存泄漏——因为没处理类型校验、引用分离、生命周期。
- 传入参数是字符串但你当整数取
zval->value.lval→ 读到垃圾值 - 修改一个被多个变量共享的
zval(比如函数参数是引用传递)→ 影响调用方,必须先SEPARATE_ZVAL_NOREF - 返回新变量却忘了调用
RETVAL_STRING或RETURN_LONG→ PHP 层收不到值,甚至 segfault
获取用户传入参数:用 zend_parse_parameters 而不是手动解包
手动从 execute_data->This 或 execute_data->func->op_array->num_args 里扒参数,既错又慢。PHP 提供了类型安全的解析宏,自动完成类型转换、NULL 检查、引用分离。
- 要读一个必需的整数:
zend_parse_parameters(ZEND_NUM_ARGS(), "l", &my_long) - 可选字符串 + 默认值:
zend_parse_parameters(ZEND_NUM_ARGS(), "|s!", &str, &str_len)(s!表示允许 NULL) - 数组参数必须用
a,拿到的是zval*,别用char*接 —— 否则 PHP 7.4+ 会直接报错Segmentation fault (core dumped) - 如果参数个数不对或类型不匹配,
zend_parse_parameters自动抛Warning并返回FAILURE,必须检查返回值
构造并返回变量:用 RETVAL_* 宏,别碰 return_value 指针裸操作
return_value 是函数调用栈上的 zval*,它的内存由 Zend VM 管理。直接赋值 return_value->value.lval = 42 很危险:类型没设、refcount 没增、可能指向只读内存。
- 返回整数:
RETVAL_LONG(123)(自动设 type = IS_LONG,refcount = 1) - 返回字符串(需复制):
RETVAL_STRING("hello")(内部调用estrndup,你不用 free) - 返回已存在的 zval(比如从数组里取出来):
RETVAL_ZVAL(src_zval, 1, 0)—— 第二个参数 1 表示 copy,第三个 0 表示不 dtor src - 千万别写
ZVAL_LONG(return_value, 42)后就 return;它不设置 type 标志位,PHP 层看到的是未定义类型,行为不可预测
修改全局变量或超全局数组:小心作用域和持久化
想在扩展里改 $_GET 或 $GLOBALS['foo']?得走 Zend API,不能用 zend_hash_find 直接改哈希桶。
立即学习“PHP免费学习笔记(深入)”;
- 改
$_SERVER:zend_is_auto_global_str("_SERVER", sizeof("_SERVER") - 1)先确认它是 auto global,再用zend_hash_str_update更新其内部zval - 设全局变量:
zend_set_local_var_str("myvar", sizeof("myvar")-1, &val, 1)(第四个参数 1 表示 copy) - 在 MINIT 阶段注册的全局变量,若用
EG(scope)或CG(active_op_array)错误上下文访问,会 crash —— 因为那时还没执行到脚本作用域 - 所有写入超全局的操作,都应避免在 RINIT 前或 RSHUTDOWN 后进行,否则
zval可能已被销毁
最常被忽略的一点:zval 的类型转换不是隐式的。比如把 IS_STRING 强转成 long,得调用 convert_to_long(),而不是直接取 zval->value.lval —— 否则 PHP 8 会触发 ZEND_ASSERT 失败。










