必须通过EX(symbol_table)获取符号表指针并检查NULL后重建,用zend_hash_str_add_new插入变量(键长用sizeof-1)、zend_hash_str_find读取并校验类型、zend_hash_str_del删除变量,全程需手动管理zend_string内存与类型安全。

要在PHP 7.1扩展中直接读写符号表(如动态创建/修改/销毁变量),必须绕过ZEND层封装,手动操作当前作用域的zend_array(即符号表底层结构),否则仅靠ZEND_API宏无法触及变量名与zval的映射关系。
获取当前作用域的符号表指针
在函数实现内部,不能直接用EG(active_symbol_table)——该宏在PHP 7.1中已被废弃且不保证线程安全;必须通过EX(symbol_table)从执行器全局上下文execute_data中提取:
第一步:在PHP_FUNCTION函数体内声明局部变量:zend_array *symbol_table = EX(symbol_table);
第二步:判断是否为空——若为NULL,说明当前在全局作用域外且未初始化符号表,需调用zend_rebuild_symbol_table()强制重建,否则后续写入会触发段错误。
立即学习“PHP免费学习笔记(深入)”;
这一步不可跳过。PHP 7.1默认延迟初始化符号表,函数刚进入时EX(symbol_table)常为NULL,直接操作会导致崩溃。
向符号表插入新变量(name → zval)
方法一:使用zend_hash_str_add_new插入字符串键名变量
zval zv; ZVAL_STRING(&zv, "hello world"); zend_hash_str_add_new(symbol_table, "myvar", sizeof("myvar")-1, &zv);
注意:键长必须传sizeof("myvar")-1,不能传strlen("myvar")——后者在编译期不可求值,会导致GCC警告并可能截断;【sizeof返回编译期常量,安全;strlen是运行时调用,PHP 7.1扩展中禁止在hash插入路径里引入额外函数调用】。
方法二:支持动态变量名(如$$var形式)
先构造zend_string:zend_string *key = zend_string_init("dynamic_name", sizeof("dynamic_name")-1, 0);,再用zend_hash_add_new(symbol_table, key, &zv);,最后必须zend_string_release(key);——漏掉这句将导致内存泄漏,PHP 7.1的GC不回收未释放的zend_string。
从符号表读取变量值并校验类型
调用zend_hash_str_find获取zval指针:
zval *found = zend_hash_str_find(symbol_table, "target_var", sizeof("target_var")-1);
必须判空:if (!found) { RETURN_FALSE; }——PHP 7.1符号表查找失败返回NULL,不抛异常,也不设error,不检查就解引用必崩。
接着验证类型是否匹配预期:if (Z_TYPE_P(found) != IS_STRING) { php_error_docref(NULL, E_WARNING, "Expected string, got %s", zend_get_type_by_const(Z_TYPE_P(found))); RETURN_FALSE; }
这一步不能省略。符号表中变量类型随时可能被上层PHP脚本修改,硬转Z_STRVAL_P(found)前必须确保它是IS_STRING,否则读越界。
删除符号表中的变量
用zend_hash_str_del移除键:
zend_hash_str_del(symbol_table, "obsolete_var", sizeof("obsolete_var")-1);
该操作立即生效,无需刷新或通知引擎——PHP 7.1的符号表是实时哈希结构,删除后下一次zend_hash_str_find必然失败。
不要用unset模拟:PHP用户层的unset($x)最终也调用此函数,但中间经过ZEND_OPCODE解析和作用域检查;扩展中直调更高效,也更危险——【若键名不存在,此函数静默失败,不报错也不返回,务必确认键存在后再删】。



















