Skip to content

Latest commit

 

History

History
183 lines (137 loc) · 7.04 KB

2.4.md

File metadata and controls

183 lines (137 loc) · 7.04 KB

2.4 变量的存储方式

(php5)

我们在前两节已经了解了PHP中变量的类型和值是怎样在内核中用C语言实现的, 这一节我们将看一下内核是怎样来组织用户在PHP中定义的变量的。

有一点对我们扩展开发者来说非常棒,那就是用户在PHP中定义的变量我们都可以在一个HashTable中找到, 当PHP中定义了一个变量,内核会自动的把它的信息储存到一个用HashTable实现的符号表里。

全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自动销毁。

当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之, 这也就是为什么我们无法在函数中使用在函数外定义的变量的原因 (因为它们分属两个符号表,一个当前作用域的,一个全局作用域的)。 如果不是在一个函数里,则全局作用域的符号表处于激活状态。

我们现在打开Zend/zend_globals.h文件,看一下_zend_execution_globals结构体,会在其中发现这么两个element:

struct _zend_executor_globals {
    ...
    HashTable symbol_table;
    HashTable *active_symbol_table;
    ...
};		
		

其中的 symbol_table元素可以通过EG宏来访问,它代表着PHP的全局变量,如$GLOBALS,其实从根本上来讲, $GLOBALS不过是EG(symbol_table)的一层封装而已。

与之对应,下面的active_symbol_table元素也可以通过EG(active_symbol_table)的方法来访问,它代表的是处于当前作用域的变量符号表。

我们上边也看到了,其实这两个成员在_zend_executor_globals里虽然都代表HashTable, 但一个是真正的HashTable,而另一个是一个指针。 当我们在对HashTable进行操作的时候,往往是把它的地址传递给一些函数。 所以,如果我们要对EG(symbol_table)的结果进行操作,往往需要对它进行求址操作然后用它的地址作为被调用函数的参数。

下面我们用一段例子来解释下上面说的理论:

<?php
$foo = 'bar';
?>
		

上面是一段PHP语言的例子,我们创建了一个变量,并把它的值设置为'bar',在以后的代码中我们便可以使用$foo变量。相同的功能我们怎样在内核中实现呢?我们可以先构思一下步骤:

  • 创建一个zval结构,并设置其类型。
  • 设置值为'bar'。
  • 将其加入当前作用域的符号表,只有这样用户才能在PHP里使用这个变量。
  • 具体的代码为:
{
    zval *fooval;

    MAKE_STD_ZVAL(fooval);
    ZVAL_STRING(fooval, "bar", 1);
    ZEND_SET_SYMBOL( EG(active_symbol_table) ,  "foo" , fooval);
}		

首先,我们声明一个zval指针,并申请一块内存。然后通过ZVAL_STRING宏将值设置为‘bar’,最后一行的作用就是将这个zval加入到当前的符号表里去,并将其label定义成foo,这样用户就可以在代码里通过$foo来使用它了。

(php7)

我们在前两节已经了解了PHP中变量的类型和值是怎样在内核中用C语言实现的, 这一节我们将看一下内核是怎样来组织用户在PHP中定义的变量的。

有一点对我们扩展开发者来说非常棒,那就是用户在PHP中定义的变量我们都可以在一个HashTable中找到, 当PHP中定义了一个变量,内核会自动的把它的信息储存到一个用HashTable实现的符号表里。

全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自动销毁。

当用户在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活之, 这也就是为什么我们无法在函数中使用在函数外定义的变量的原因 (因为它们分属两个符号表,一个当前作用域的,一个全局作用域的)。 如果不是在一个函数里,则全局作用域的符号表处于激活状态。

我们现在打开Zend/zend_globals.h文件,看一下_zend_execution_globals结构体,会在其中发现这么两个element:

struct _zend_executor_globals {
...
	zval          *vm_stack_top;
	zval          *vm_stack_end;
	zend_vm_stack  vm_stack;

	struct _zend_execute_data *current_execute_data;
	zend_class_entry *scope;

	zend_long precision;
...
};	
	
struct _zend_execute_data {
	const zend_op       *opline;           /* executed opline                */
	zend_execute_data   *call;             /* current call                   */
	zval                *return_value;
	zend_function       *func;             /* executed funcrion              */
	zval                 This;             /* this + call_info + num_args    */
	zend_class_entry    *called_scope;
	zend_execute_data   *prev_execute_data;
	zend_array          *symbol_table;
#if ZEND_EX_USE_RUN_TIME_CACHE
	void               **run_time_cache;   /* cache op_array->run_time_cache */
#endif
#if ZEND_EX_USE_LITERALS
	zval                *literals;         /* cache op_array->literals       */
#endif
};


//info.c中例子如下
PHPAPI void php_print_info(int flag)
{
	...
	php_print_gpcse_array(ZEND_STRL("_REQUEST"));
	php_print_gpcse_array(ZEND_STRL("_GET"));
	...	
}

static void php_print_gpcse_array(char *name, uint name_length)
{
	zval *data, *tmp, tmp2;
	zend_string *string_key;
	zend_ulong num_key;
	zend_string *key;

	key = zend_string_init(name, name_length, 0);
	zend_is_auto_global(key);

	if ((data = zend_hash_find(&EG(symbol_table), key)) != NULL && (Z_TYPE_P(data) == IS_ARRAY)) {
	...
}

/* Executor */
#ifdef ZTS
# define EG(v) ZEND_TSRMG(executor_globals_id, zend_executor_globals *, v)
#else
# define EG(v) (executor_globals.v)
extern ZEND_API zend_executor_globals executor_globals;
#endif		

其中的 symbol_table元素可以通过EG宏来访问,它代表着PHP的全局变量可以通过EG(symbol_table)来访问, symbol_table由原来的HashTable修改为了现在的zend_array。

当前的符号表可以通过zend_rebuild_symbol_table()取得,原有的active_symbol_table已经删除了。

下面我们用一段例子来解释下上面说的理论:

<?php
$foo = 'bar';
?>
		

上面是一段PHP语言的例子,我们创建了一个变量$foo,并把它的值设置为'bar',在以后的代码中我们便可以使用$foo变量。相同的功能我们怎样在内核中实现呢?我们可以先构思一下步骤:

  • 创建一个zval结构,并设置其类型。
  • 设置值为'bar'。
  • 将其加入当前作用域的符号表,只有这样用户才能在PHP里使用这个变量。
  • 具体的代码为:
{
    zval fooval;
    ZVAL_STRING(&fooval, "bar" );
    zend_set_local_var_str("foo", sizeof("foo")-1, &fooval, 0);
}		

首先,我们声明一个zval变量。然后通过ZVAL_STRING宏将值设置为"bar",最后一行的作用就是将这个zval加入到当前的符号表里去,并将其变量名定义成foo,这样用户就可以在代码里通过$foo来使用它了。

links