描述
PHP中把定义在函数、类之外的变量称之为全局变量,也就是定义在主脚本中的变量,这些变量可以在函数、成员方法中通过global关键字引入使用。
1 function test() { 2 global $id; 3 $id++; 4 } 5 6 $id = 1; 7 test(); 8 echo $id;
存储
全局变量在整个请求执行期间始终存在,它们保存在EG(symbol_table)
中,也就是全局变量符号表,与静态变量的存储一样,这也是一个哈希表,主脚本(或include、require)在zend_execute_ex
执行开始之前会把当前作用域下的所有局部变量添加到EG(symbol_table)
中
zend_vm_execute.h中,i_init_execute_data()
这个函数中会把局部变量插入到EG(symbol_table):
1 ZEND_API void zend_execute(zend_op_array *op_array, zval *return_value) 2 { 3 ... 4 i_init_execute_data(execute_data, op_array, return_value); 5 zend_execute_ex(execute_data); 6 ... 7 }
i_init_execute_data会把局部变量插入到EG(symbol_table),定义在zend_execute.c
1 static zend_always_inline void i_init_code_execute_data(zend_execute_data *execute_data, zend_op_array *op_array, zval *return_value) /* {{{ */ 2 { 3 ZEND_ASSERT(EX(func) == (zend_function*)op_array); 4 5 EX(opline) = op_array->opcodes; 6 EX(call) = NULL; 7 EX(return_value) = return_value; 8 9 zend_attach_symbol_table(execute_data); 10 11 if (!op_array->run_time_cache) { 12 op_array->run_time_cache = emalloc(op_array->cache_size); 13 memset(op_array->run_time_cache, 0, op_array->cache_size); 14 } 15 EX_LOAD_RUN_TIME_CACHE(op_array); 16 EX_LOAD_LITERALS(op_array); 17 18 EG(current_execute_data) = execute_data; 19 }
zend_attach_symbol_table 把局部变量插入到EG(symbol_table),定义在zend_execute_API.c中
1 ZEND_API void zend_attach_symbol_table(zend_execute_data *execute_data) /* {{{ */ 2 { 3 zend_op_array *op_array = &execute_data->func->op_array; 4 HashTable *ht = execute_data->symbol_table; //全局变量符号表 5 6 /* copy real values from symbol table into CV slots and create 7 INDIRECT references to CV in symbol table */ 8 if (EXPECTED(op_array->last_var)) { 9 zend_string **str = op_array->vars; //局部变量 10 zend_string **end = str + op_array->last_var;//最后一个局部变量的位置 11 zval *var = EX_VAR_NUM(0); 12 13 do { 14 zval *zv = zend_hash_find(ht, *str); 15 16 if (zv) { 17 if (Z_TYPE_P(zv) == IS_INDIRECT) { 18 zval *val = Z_INDIRECT_P(zv); 19 20 ZVAL_COPY_VALUE(var, val); 21 } else { 22 ZVAL_COPY_VALUE(var, zv); 23 } 24 } else { 25 ZVAL_UNDEF(var); 26 zv = zend_hash_add_new(ht, *str, var);//添加到全局变量符号表 27 } 28 ZVAL_INDIRECT(zv, var); 29 str++;//指向下一个局部变量 30 var++; 31 } while (str != end); 32 } 33 }
注意局部变量通过偏移量来访问,而不是变量名
从上面的过程可以很直观的看到,在执行前遍历局部变量,然后插入EG(symbol_table),EG(symbol_table)中的value直接指向局部变量的zval,示例经过这一步的处理之后(此时局部变量只是分配了zval,但还未初始化,所以是IS_UNDEF):
访问
与静态变量的访问一样,全局变量也是将原来的值转换为引用,然后在global导入的作用域内创建一个局部变量指向该引用:
1 global $id; // 相当于:$id = & EG(symbol_table)["id"];
销毁
局部变量如果没有手动销毁,那么在函数执行结束时会将它们销毁,而全局变量则是在整个请求结束时才会销毁,即使是我们直接在PHP脚本中定义在函数外的那些变量。
1 void shutdown_destructors(void) 2 { 3 if (CG(unclean_shutdown)) { 4 EG(symbol_table).pDestructor = zend_unclean_zval_ptr_dtor; 5 } 6 zend_try { 7 uint32_t symbols; 8 do { 9 symbols = zend_hash_num_elements(&EG(symbol_table)); 10 //销毁 11 zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor); 12 } while (symbols != zend_hash_num_elements(&EG(symbol_table))); 13 } 14 ... 15 }