变量的作用域
讨论完 PHP 7 的变量实现和变量的类型,接下来讨论一下变量的作用域。什么是变量的作用域呢?简单地说,就是定义变量在代码中可以使用的范围。那么在 PHP 7 中变量和变量的作用域又是如何实现的呢?
全局变量
简单地说,全局变量就是在程序的任何一处都可以使用的变量。在 PHP 底层维护了全局的符号表(symbol_table
),它本身是一个 HashTable
, PHP 代码中的全局变量都维护在这个 HashTable
中,符号表的作用域是整个 PHP 代码。
为了方便理解,下面以一段 PHP 代码为例,并通过 gdb
来进行追踪:
<?php
$a = "hello world";
在上面的 php 文件中只有一个句话,通过 gdb
一步步追踪,最终发现会调用 zend_attach_symbol_table
这个函数,这个函数会将 “$a”
加到全局的符号表中。
限于篇幅,这里只将 zend_attach_symbol_table
函数的核心代码展示出来,如下面所示:
ZEND_API void zend_attach_symbol_table(zend_execute_data *execute_data) /* {{{ */
{
zend_op_array *op_array = &execute_data->func->op_array;
HashTable *ht = execute_data->symbol_table;
/* copy real values from symbol table into CV slots and create INDIRECT references to CV in symbol table */
if (EXPECTED(op_array->last_var)) {
//代码省略//
do {
zval *zv = zend_hash_find(ht, *str);
if (zv) {
if (Z_TYPE_P(zv) == IS_INDIRECT) {
zval *val = Z_INDIRECT_P(zv);
ZVAL_COPY_VALUE(var, val);
//代码省略//
|
局部变量
局部变量是在函数内部定义说明的。函数的调用过程是不断地压栈和出栈,出栈后内部变量被销毁,因此其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
为了便于理解,现以 PHP 代码为例说明一下,如下所示:
<?php
class User
{
public function hi()
{
$a = 100;
$b = time();
}
}
这里定义了一个类,在类中实现了一个方法,方法中定义了两个变量 a
、b
。因为变量 a
和 b
在方法内部,其他地方不能直接访问,所以只能在方法内部使用。
中间变量
在 PHP 代码中有一种操作,会产生一种类型为 IS_TMP_VAR
的变量,姑且称为 “中间变量”。它的产生比较简单,以如下代码为例:
<?php
$a = 1;
$b = $a + 1;
代码中定义了一个变量,接着做了一次加法运算,然后通过 vld
查看它的 opcode
,查看时将 -dvld.verbosity=1
参数加上。
vld
查看的 opcode
如下:
Finding entry points
Branch analysis from position: 0
Add 0
Add 1
Add 2
Add 3
Jump found. (Code = 62) Position 1 = -2
filename: /home/vagrant/test.php
function name: (null)
number of ops: 4
compiled vars: !0 = $a, !1 = $b
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > ASSIGN OP1[ IS_CV !0 ] OP2[ , IS_CONST (0) 1 ]
3 1 ADD RES[ IS_TMP_VAR ~2 ] OP1[ IS_CV !0 ] OP2[ , IS_CONST (0) 1 ]
2 ASSIGN OP1[ IS_CV !1 ] OP2[ , IS_TMP_VAR ~2 ]
5 3 > RETURN OP1[ IS_CONST (0) 1 ]
branch: # 0; line: 2- 5; sop: 0; eop: 3; out1: -2
path #1: 0,
通过 vld
可以看到,$a + 1
执行后生成一个中间变量(类型为 IS_TMP_VAR
),然后将中间变量赋值给 $b
,这个中间过程用户无法感知,它仅在当前作用域内有效。
静态变量
静态变量是在 PHP 代码中使用的场景较多的一种变量。下面同样以一段 PHP 代码为例,然后再看看它底层的原理,代码如下面所示:
<?php
class A {
public function test($ab){
static $n = 10;
$n = $n * $ab;
return $n;
}
}
$obj = new A();
$i = 1;
while($i < 10){
echo "number $i return ". $obj->test($i) ."\n";
$i++;
}
通过执行上面的代码可以发现静态变量 $n
的值在函数 test
结束后并没有被销毁。PHP 代码执行过程中会将局部变量存储在 zend_execute_data
相邻的内存中,但是静态变量会存在 _zend_op_array.static_variables
中。局部变量在函数执行结束后被销毁,而静态变量不会被销毁。