**
1
ZendEngine存储变量是变量名和变量值分开存储!
2
作用域:1全局符号表 活动符号表来存变量名
**
3
数组,符号表,对象属性,函数表,符号表,等等都是用HashTable来做为容器的.
变量名存在符号表中,对应的值存在结构体zval中,将变量名与值再hashmap映射。
不同作用域的变量名存在不同的符号表中(这样可以实现不同作用域的变量不干扰),key为变量名,value为指向zval的指针。
struct _zend_executor_globals {
....
HashTable *active_symbol_table;/*活动符号表*/
HashTable symbol_table; /*全局符号表*/
HashTable included_files;
jmp_buf *bailout;
int error_reporting;
.....
}
其中,全局符号表,保存了在顶层作用域(就是不在任何函数,对象内)的变量。每当调用一个函数(对象的方法)的时候,就会为这个函数创建一个活动符号表,所有在这个函数内定义的变量,都会保存在这个活动符号表中。
对,这就是PHP的变量作用域的实现方式! 举个列子:
<?php
$var = "I am in the global symbol table";
function sample($para){
$var = "I am in the active symbol table";
echo $var;
}
sample($var);
echo $var;
?>
在函数sample外面的变量
var,它会被填入全局符号表中,与他对应的有一个zval指针,这个zval保存了一个字符串”Iamintheglobalsymboltable”.函数内的
var, 它会被填入属于函数sample的活动符号表中,一样的,与他对应的zval中,保存着字符串”I am in the active symbol table“.
比较特殊的,就是函数sample的参数
para了,这个
para是保存在sample的活动符号表的,但是与他对应的zval指针,会指向一个保存一份全局变量$var的copy的zval(严格来讲不是copy,是引用,这个涉及到变量的copy on write机制,我会在以后介绍)。
zval结构
引用:
在PHP中,所有的变量都是用一个结构-zval来保存的, 在Zend/zend.h中我们可以看到zval的定义:
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount;
zend_uchar type;
zend_uchar is_ref;
} zval;
其中zvalue_value是真正保存数据的关键部分,现在到了揭晓谜底的时候了,PHP是如何在ZE的基础上实现弱类型的呢? 因为zvalue_value是个联合体(union),
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
那么这个结构是如何储存PHP中的多种类型的呢?
PHP中常见的变量类型有:
1. 整型/浮点/长整型/bool值 等等
2. 字符串
3. 数组/关联数组
4. 对象
5. 资源
PHP根据zval中的type字段来储存一个变量的真正类型,然后根据type来选择如何获取zvalue_value的值,比如对于整型和bool值:
zval.type = IS_LONG;//整形
zval.type = IS_BOOL;//布尔值
就去取zval.value.lval,对于bool值来说lval∈(0|1);
如果是双精度,或者float则会去取zval.value的dval。
而如果是字符串,那么:
zval.type = IS_STRING
这个时候,就会取:
zval.value.str
而这个也是个结构,存有C分格的字符串和字符串的长度。
而对于数组和对象,则type分别对应IS_ARRAY, IS_OBJECT, 相对应的则分别取zval.value.ht和obj
比较特别的是资源,在PHP中,资源是个很特别的变量,任何不属于PHP内建的变量类型的变量,都会被看作成资源来进行保存,比如,数据库句柄,打开的文件句柄等等。 对于资源:
type = IS_RESOURCE
这个时候,会去取zval.value.lval, 此时的lval是个整型的指示器, 然后PHP会再根据这个指示器在PHP内建的一个资源列表中查询相对应的资源(这部分的内容,我以后会单独开一个篇文章来介绍),目前,你只要知道此时的lval就好像是对应于资源链表的偏移值。
ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type);
借用这样的机制,PHP就实现了弱类型,因为对于ZE的来说,它所面对的永远都是同一种类型,那就是zval。
引用
Hashtable是PHP的核心结构(了解Hashtable, 可以参看我之前的文章深入理解PHP之数组(遍历顺序)), 数组也是用她来表示的, 而符号表也是一种关联数组, 对于如下代码:
var_dump("I am Laruence,");
var_dump(memory_get_usage());
$array = array_fill(1, 100, "laruence");
foreach ($array as $key => $value) {
${$value . $key} = NULL;
}
var_dump(memory_get_usage());
foreach ($array as $key=> $value) {
unset(${$value . $key});
}
var_dump(memory_get_usage());
我们定义了100个变量, 然后又按个Unset了他们, 来看看输出:
string(43) "I am Laruence"
int(93560)
int(118848)
int(104448)
Wow, 怎么少了这么多内存?
这是因为对于Hashtable来说, 定义它的时候, 不可能一次性分配足够多的内存块, 来保存未知个数的元素, 所以PHP会在初始化的时候, 只是分配一小部分内存块给HashTable, 当不够用的时候再RESIZE扩容,而Hashtable, 只能扩容, 不会减少, 对于上面的例子, 当我们存入100个变量的时候, 符号表不够用了, 做了一次扩容, 而当我们依次unset掉这100个变量以后, 变量占用的内存是释放了(118848 – 104448), 但是符号表并没有缩小, 所以这些少的内存是被符号表本身占去了…