1、PHP变量的底层实现
PHP代码执行图解
1.1 变量在内存中的存储结构
PHP变量是通过zval结构体来存储的,文件: Zend/zend.h 316行左右
1.2 值的存储
PHP变量的值是放在zval结构体中的value段中的,文件: Zend/zend.h
1.3 结构体的字段解释
struct _zval_struct { /* Variable information */ zvalue_value value;/*变量的值,是个联合体*/ zend_uint refcount__gc; /*指向次数*/ zend_uchar type;/* 变量类型 */ zend_uchar is_ref__gc; /*是否引用*/ }; //type字段的值为以下常量 IS_NULL, IS_BOOL,IS_LONG,IS_DOUBLE IS_STRING,IS_ARRAY,IS_OBJECT IS_RESOURCE
1.4 联合体中的值
typedef union _zvalue_value { long lval;/* long value */ double dval;/* double value */ struct { char *val; int len; } str; HashTable *ht;/* hash table value */ zend_object_value obj; } zvalue_value;
联合中为什么只列出了5种值?
NULL不用,zval的type为IS_NULL即可
Bool以1,0存储在lval上
resource的type为resource,其resource的内容用long来标志(资源标记)
1.5 变量的结构图
1.6 变量的创建
创建变量的步骤: $str = "hello";
1:创建zval结构,并设置其类型 IS_STRING
2:设置其值为 hello
3:讲其加入符号表
{ zval *fooval; MAKE_STD_ZVAL(fooval); ZVAL_STRING(fooval, "hello", 1); ZEND_SET_SYMBOL( EG(active_symbol_table) , "foo" , fooval); }
1.7 符号表 symbol_table
符号表是什么?
符号表是一张哈希表,里面存储了变量名->变量的zval结构体的地址,
// zend/zend_globals.h 182行 struct _zend_executor_globals { ... ... HashTable *active_symbol_table; /*活动符号表*/ HashTable symbol_table;/* 全局符号表 */ HashTable included_files;/* files already included */
1.8 符号表与函数
Zend/zend_compiles.h struct _zend_execute_data { ... zend_op_array *op_array; //函数的执行步骤 HashTable *symbol_table; // 此函数的符号表地址 zend_class_entry *current_scope; zval *current_this; zval *current_object; ... };
上面这个,是当前函数执行时的符号表
1.9 符号表与作用域
当执行到函数时,会生成函数的"执行环境结构体",包含函数名,参数,执行步骤,所在的类(如果是方法),以及为这个函数生成一个符号表.符号表统一放在栈上.并把active_symbol_table指向刚产生的符号表
1.10 函数中静态变量的实现
2.0 常量-常量结构体
结构体 Zend/constants.h 33行
typedef struct _zend_constant { zval value; //变量结构体 int flags; //标志,是否大小写敏感等 char *name; //常量名 uint name_len; int module_number;//模块名 } zend_constant;
2.1 常量的生成
int zend_register_constant(zend_constant *c TSRMLS_DC) { ... ... zend_hash_add(EG(zend_constants), name, c->name_len, (void *) c, sizeof(zend_constant), NULL)==FAILURE) ... ... }
2.2 define函数的实现
define函数当然是调用zend_register_constant声明的常量 :)
具体如下 Zend/zend_builtin_functions.c
关键代码:
c.value = *val; zval_copy_ctor(&c.value); if (val_free) { zval_ptr_dtor(&val_free); } c.flags = case_sensitive; /* non persistent */ c.name = zend_strndup(name, name_len); c.name_len = name_len+1; c.module_number = PHP_USER_CONSTANT; if (zend_register_constant(&c TSRMLS_CC) == SUCCESS) { RETURN_TRUE; } else { RETURN_FALSE; }
2.3 有趣的测试
1:常量并没有检测名字...
define('^_^',"laugh");
2:常量的第2个参数还可以是对象,与手册上介绍的不同
define('obj',new className());
(当然,对象要有toString方法等着)
2.4 常量为什么是全局有效的?
很简单,常量的哈希表只有一个
EG(zend_constants)
3.0 内存管理与垃圾回收
PHP封装了对系统内存的请求,不要直接用malloc直接请求内存
3.1 PHP的hashtable太强大
3.2 引用计数
<?php $a = 1; $b = $a;
$a,$b的值及类型 都一样,有必要再申请一个zval结构吗?
3.3 引用计数解释
<?php $a = 1; $b = $a;
当$a的值赋给$b时,并没有为$b生成一个新的zval结构体.而是$b与$a共享一个结构体.
3.4:copy-on-write 写时复制
<?php $a = 1; $b = $a; $b=6
3.5 引用传值发生了什么?
<?php $a = 1; $b = &$a;
3.6 引用传值为什么影响了2个值
<?php $a = 1; $b = &$a; $b = 6;
内核修改zval的值时,发现is_ref_gc为1,则直接修改该value,而不是复制一份.如下图: