前言

单位里有一套老代码,写了这么一个换库逻辑。

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的更多相关文章

  1. PHP中超全局变量$GLOBALS和global的区别

    一.超全局变量$GLOBALS PHP超全局变量有很多,如下的都属于超全局变量(Superglobal): $GLOBALS,$_SERVER,$_GET,$_POST,$_FILES,$_COOKI ...

  2. PHP中$GLOBALS和global的区别

    很多人都认为$GLOBALS['var']和global $var只是写法上不同,其实并不是这样 根据官方的解释是  $GLOBALS['var']是外部全局变量$var的本身, 而global $v ...

  3. php中global与$GLOBALS的用法及区别-转载

    php中global 与 $GLOBALS[""] 差别 原本觉得global和$GLOBALS除了写法不一样觉得,其他都一样,可是在实际利用中发现2者的差别还是很大的! 先看下面 ...

  4. php中global与$GLOBALS的用法及区别

    php中global 与 $GLOBALS[""] 差别 原本觉得global和$GLOBALS除了写法不一样觉得,其他都一样,可是在实际利用中发现2者的差别还是很大的! 先看下面 ...

  5. python简单实现用户表单登录

    实现简单的用户表单验证登录 user="desperado" pwd=" s=0 for i in range(10): if s < 3: username = ...

  6. c语言描述简单的线性表,获取元素,删除元素,

    //定义线性表 #define MAXSIZE 20 typedef int ElemType; typedef struct { ElemType data[MAXSIZE]; //这是数组的长度, ...

  7. 简单的多表插入(oracle)

    简单的多表插入语句: insert all into 表1(字段1,2...) values(值1,值2......) into 表2(字段1,2...)) values(值1,值2......) s ...

  8. 15.5 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表

    点击返回:自学Zabbix之路 自学Zabbix之路15.5 Zabbix数据库表结构简单解析-其他 表  1. Actions表 actions表记录了当触发器触发时,需要采用的动作. 2.Aler ...

  9. 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表、Hosts_groups表、Interface表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.1 Zabbix数据库表结构简单解析-Hosts表.Hosts_grou ...

  10. 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix之路15.2 Zabbix数据库表结构简单解析-Items表 Items表记录了i ...

随机推荐

  1. configure: error: Can't find GL/gl.h. Look for Mesa devel packages for your distro.

    1. 安装文件查询工具 sudo apt install plocate 2. 查询头文件地址,shell命令: locate GL/gl.h 3. 为编译时指定其他的头文件查询地址: export ...

  2. php json_decode 和json_encode

    json_decode (PHP 5 >= 5.2.0, PECL json >= 1.2.0) json_decode - 对 JSON 格式的字符串进行编码 说明 mixed json ...

  3. 位移运算符 1<<4

    转载自 https://www.cnblogs.com/ljangle/p/13036992.html 位运算 1 << 4   static final int DEFAULT_INIT ...

  4. bat2exe

    https://blog.csdn.net/qq_23452385/article/details/109145151

  5. 工作 6 年,@Transactional 注解用的一塌糊涂

    接手新项目一言难尽,别的不说单单就一个 @Transactional 注解用的一塌糊涂,五花八门的用法,很大部分还失效无法回滚. 有意识的在涉及事务相关方法上加@Transactional注解,是个好 ...

  6. 《SpringBoot事务》单机mybatis事务控制

    一.场景再现 我们同时操作两张表及以上,一张表操作成功后,第二张表操作失败,此时需要回滚上一次的操作. 或者两次操作都成功,但后续业务处理的时候异常,此时需要回滚前面的所有数据库操作. 二.代码 1 ...

  7. 事务TRANSACTION 1

    MySQL事务是指一组SQL语句作为一个工作单元,同生共死.类似JS中的Array.prototype.every方法 事务的特点 原子性(Atomicity):即事务中的SQL像原子一样牢不可破,无 ...

  8. 使用vue-cli4快速搭建项目环境、使用webpack4打包自己的library类库、封装vue插件并发布

    快速创建 使用官方推荐的vue-cli创建项目如下: # 安装 Vue Cli npm install -g @vue/cli # 创建一个项目 vue create vanttest # 创建完成后 ...

  9. LeetCode题集-4 - 寻找两个有序数组的中位数,图文并茂,六种解法,万字讲解

    题目:给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2.请你找出并返回这两个正序数组的 中位数 . 算法的时间复杂度应该为 O(log (m+n)) . 作为目前遇到 ...

  10. 全面掌握 Jest:从零开始的测试指南(下篇)

    在上一篇测试指南中,我们介绍了Jest 的背景.如何初始化项目.常用的匹配器语法以及钩子函数的使用.这一篇篇将继续深入探讨 Jest 的高级特性,包括 Mock 函数.异步请求的处理.Mock 请求的 ...