PHP中$GLOBALS和global的区别,简单了解符号表、zval
前言
单位里有一套老代码,写了这么一个换库逻辑。
function conn() {
global $conn;
if ($conn) {
unset($conn);
}
$conn = mysqli_connect...;
return $conn;
}
这套代码之前的换库操作,都是使用的返回值的$conn,一直相安无事。直到有新同事接手,用了另一个封装使用global $conn的查询方法,就出了问题。
按照这个设计,理论上应当是在任意位置都可以使用唯一的global $conn。那么为什么会出错呢,这就涉及到PHP中,global关键字,和$GLOBALS全局变量的区别陷阱了。
现象
$b = 2;
function aa()
{
global $a;
$a = 1;
unset($a);
unset($GLOBALS['b']);
}
aa();
echo $a, PHP_EOL;
echo $GLOBALS['a'], PHP_EOL;
var_dump($b);
输出:
1
1
Null
解释
在函数中,使用global关键字时,实际上是生成了一个局部变量,指向对应的全局变量,如果全局变量不存在,会先生成对应的全局变量,再指向它。
如果对这个局部变量使用unset,实际上只是将这个局部变量销毁,并不会影响到全局变量。
也即是说,global $a 和 $a = &$GLOBALS['a'] 在使用和表现形式上是等价的。因底层实现有区别,推荐使用gloabal关键字,消耗较少。
但使用$GLOBALS超全局变量时,$GLOBALS['b'] 和 全局的 $b 是完全一致的,使用unset会被影响。
底层原理
简而言之,
- $GLOBALS['a'],是全局$a本身。K存储在全局符号表中,K(a)->zval(a)。
- 函数中global $a,是指向全局$a的局部符号。K存储在局部符号表中,K->zval(a)。
- 函数中$_a = &$GLOBALS['a'],是指向全局$a的局部变量。K存储在局部变量表中,K->zval(_a)->zval(a)。
zval
每一个变量,最终指向的都是zval。
简单介绍
zval 是 PHP 的核心数据结构之一,用于表示 PHP 变量的内部实现。PHP 是一种弱类型语言,变量的类型可以动态变化,而 zval 则是实现这种动态类型的关键结构。它不仅存储变量的值,还包含了类型信息、引用计数等元数据。
struct _zval_struct {
zend_value value; /* value */
union {
uint32_t type_info;
struct {
ZEND_ENDIAN_LOHI_3(
uint8_t type, /* active type */
uint8_t type_flags,
union {
uint16_t extra; /* not further specified */
} u)
} v;
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* cache slot (for RECV_INIT) */
uint32_t opline_num; /* opline number (for FAST_CALL) */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t guard; /* recursion and single property guard */
uint32_t constant_flags; /* constant flags */
uint32_t extra; /* not further specified */
} u2;
};
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
...
zval 的基本结构
变量值 (value):存储变量的实际值,可以是整型、浮点型、字符串、数组、对象等。
变量类型 (type):表示当前变量的类型,如 IS_LONG (整型)、IS_STRING (字符串)、IS_ARRAY (数组) 等。
引用计数 (refcount):用来跟踪有多少个变量引用了该 zval,用于实现引用计数的垃圾回收机制。
引用标志 (is_ref):表示该 zval 是否是一个引用。如果变量被引用了(例如使用 &),这个标志会被设置。
zval 中的重要概念
写时复制(Copy-On-Write, COW)、引用计数和垃圾回收、动态类型转换。
符号表
在PHP中,变量的管理是通过符号表(Symbol Table)来实现的。符号表是一个哈希表,存储变量名和变量的值(实际上是一个 zval 结构)。每个作用域(如全局作用域、函数作用域)都有自己的符号表。
可以把符号表简单的理解为一个KV结构。变量名为K,zval为V。
在 PHP 脚本开始执行时,全局符号表就会被创建。也就是$GLOBALS。
局部符号表是在进入函数或方法时动态创建的。当函数调用结束时,局部符号表会被销毁。
哈希表
HashTable结构体,也叫zend_array,同样是_zend_value中的一种数据类型。
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
uint8_t flags,
uint8_t _unused,
uint8_t nIteratorsCount,
uint8_t _unused2)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
union {
uint32_t *arHash; /* hash table (allocated above this pointer) */
Bucket *arData; /* array of hash buckets */
zval *arPacked; /* packed array of zvals */
};
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
};
题外话,符号表的生命周期,Swoole相关
PHP中,有一个叫EG的宏定义,用于存储PHP执行引擎的全局状态信息,符号表就存储在EG的结构体_zend_executor_globals中。
struct _zend_executor_globals {
...
zend_array symbol_table; /* main symbol table */
}
_zend_executor_globals 是 PHP 执行引擎的全局结构体,包含了所有与脚本执行相关的状态信息。该结构体被设计为线程局部存储(TLS),在多线程环境中,每个线程都有自己独立的 executor_globals 实例。
由于EG是线程共享的,所以Swoole协程中,对($GLOBALS、$_SESSION、$_SERVER)等的操作会互相污染,这也是为什么Swoole对全局变量,$_SESSION等操作要单独隔离处理的原因。
global $a 和 $a = &$GLOBALS['a'] 的区别
符号表绑定 vs. 引用赋值:
global $a; 是通过符号表来实现的,局部作用域中的 $a 直接指向全局符号表中的同名变量。这意味着局部变量 $a 是全局变量 $a 的一个别名。
$a = &$GLOBALS['a']; 是通过显式的引用赋值来实现的,局部变量 $a 被赋值为全局符号表中 $a 的引用。虽然效果是一样的,但这里涉及的是通过 $GLOBALS 数组的访问。
底层操作差异:
global $a; 只是修改了局部符号表的指针,使得局部符号表中的 $a 指向全局符号表中的变量。
$a = &$GLOBALS['a']; 则是通过引用赋值操作,直接修改局部变量的 zval 引用,使它与 $GLOBALS['a'] 指向同一个 zval。
执行效率:
global $a; 的执行稍微快一些,因为它仅仅修改符号表中的指针。
$a = &$GLOBALS['a']; 需要通过 $GLOBALS 数组进行一次查找操作,然后执行引用赋值,因此可能稍微慢一些。
PHP中$GLOBALS和global的区别,简单了解符号表、zval的更多相关文章
- PHP中超全局变量$GLOBALS和global的区别
一.超全局变量$GLOBALS PHP超全局变量有很多,如下的都属于超全局变量(Superglobal): $GLOBALS,$_SERVER,$_GET,$_POST,$_FILES,$_COOKI ...
- PHP中$GLOBALS和global的区别
很多人都认为$GLOBALS['var']和global $var只是写法上不同,其实并不是这样 根据官方的解释是 $GLOBALS['var']是外部全局变量$var的本身, 而global $v ...
- php中global与$GLOBALS的用法及区别-转载
php中global 与 $GLOBALS[""] 差别 原本觉得global和$GLOBALS除了写法不一样觉得,其他都一样,可是在实际利用中发现2者的差别还是很大的! 先看下面 ...
- php中global与$GLOBALS的用法及区别
php中global 与 $GLOBALS[""] 差别 原本觉得global和$GLOBALS除了写法不一样觉得,其他都一样,可是在实际利用中发现2者的差别还是很大的! 先看下面 ...
- python简单实现用户表单登录
实现简单的用户表单验证登录 user="desperado" pwd=" s=0 for i in range(10): if s < 3: username = ...
- c语言描述简单的线性表,获取元素,删除元素,
//定义线性表 #define MAXSIZE 20 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; //这是数组的长度, ...
- 简单的多表插入(oracle)
简单的多表插入语句: insert all into 表1(字段1,2...) values(值1,值2......) into 表2(字段1,2...)) values(值1,值2......) s ...
- 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表
点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表 1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...
- 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表、Hosts_groups表、Interface表
点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表.Hosts_grou ...
- 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表
点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表 Items表记录了i ...
随机推荐
- 在计算框架MindSpore中手动保存参数变量(Parameter 变量)—— from mindspore.train.serialization import save_checkpoint
本文参考内容: https://www.mindspore.cn/doc/programming_guide/zh-CN/r1.2/advanced_usage_of_checkpoint.html? ...
- CPU端多进程/多线程调用CUDA是否可以加速???
相关: NVIDIA显卡cuda的多进程服务--MPS(Multi-Process Service) tensorflow1.x--如何在C++多线程中调用同一个session会话 tensorflo ...
- vscode中文环境配置
1.背景 2.配置 2.1.安装中文包 如果没有按照中文插件需要先按照中文插件 如果你是首次安装,安装完成后会引导你重启,就可以了 2.2.设置成中文环境 打开VSCode软件,按快捷键[Ctrl+S ...
- 重磅预告!Apache DolphinScheduler 3.2.0 新功能“剧透”
近期,Apache DolphinScheduler 将迎来 3.2.0 版本的到来.本次发版为大版本发布,将会带来众多大家期待已久的新功能和新改进.为了让用户提前感知到新版本的变化,社区特意提前&q ...
- games101 作业1及作业2分析及解决
games101 作业1及作业2分析及解决 去年的时候把games101的课程以及作业完成,但是整个过程比较粗略,也借助了不少外界的力量(doge),于是最近准备抽几天集中再把作业(1-7)过一遍,常 ...
- 代码随想录Day16
513.找树左下角的值 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值. 假设二叉树中至少有一个节点. 示例 1: 输入: root = [2,1,3] 输出: 1 示 ...
- C# 导出Excel NPOI 修改指定单元格的样式 或者行样式
参考文章:原文链接:https://blog.csdn.net/chensirbbk/article/details/52189985 #region 2.NPOI读取Excel 验证Excel数据的 ...
- Python wheel
在 Python 的生态系统中,wheel 是一种打包格式,用于分发和安装 Python 项目.它是 Python 包的标准格式之一,旨在提高安装速度和可靠性. Wheel 的优势 快速安装:因为 w ...
- Web 国际化:新增越南语语系(vue i18n)
前提: 1. 在src/locales文件夹中,新增vi.json文件 背景: 1. vue 步骤: 1. 在main.js中, import VueI18n from 'vue-i18n' Vue. ...
- 【YashanDB数据库】Mybatis-plus分页框架识别不到Yashandb
问题描述 Mybatis-plus 无法识别Yashandb数据库,应用有如下报错. 问题分析 从Mybatis-plus源码里面看到,getDBtype函数是没有Yashandb的方言. 当Yash ...