php变量
在php中变量类型和值是通过c语言实现的,那php内核具体是怎么实现的呢?
HashTable在php内核中广泛被使用,而变量就是存储在hashtable实现的符号表中.
当在PHP中调用一个函数或者类时,内核会创建一个新的符号表,这也是为什么在函数中无法使用函数外部定义的变量的原因。(因为他们分属两个符号表,一个当前作用域,一个全局作用域)
内核中作用域的定义,PHP的所有 局部变量,全局变量,函数,类的 Hash表 都在这里定义了
struct _zend_executor_globals { zval **return_value_ptr_ptr; zval uninitialized_zval; zval *uninitialized_zval_ptr; zval error_zval; zval *error_zval_ptr; zend_ptr_stack arg_types_stack; /* symbol table cache */ HashTable *symtable_cache[SYMTABLE_CACHE_SIZE]; HashTable **symtable_cache_limit; HashTable **symtable_cache_ptr; zend_op **opline_ptr; HashTable *active_symbol_table; //局部变量 HashTable symbol_table; /* main symbol table */ //全局变量 HashTable included_files; /* files already included */ //include的文件 JMP_BUF *bailout; int error_reporting; int orig_error_reporting; int exit_status; zend_op_array *active_op_array; HashTable *function_table; /* function symbol table */ //函数表 HashTable *class_table; /* class table */ //类表 HashTable *zend_constants; /* constants table */ //常量表 zend_class_entry *scope; zend_class_entry *called_scope; /* Scope of the calling class */ zval *This; long precision; int ticks_count; zend_bool in_execution; HashTable *in_autoload; zend_function *autoload_func; zend_bool full_tables_cleanup; /* for extended information support */ zend_bool no_extensions; #ifdef ZEND_WIN32 zend_bool timed_out; OSVERSIONINFOEX windows_version_info; #endif HashTable regular_list; HashTable persistent_list; zend_vm_stack argument_stack; int user_error_handler_error_reporting; zval *user_error_handler; zval *user_exception_handler; zend_stack user_error_handlers_error_reporting; zend_ptr_stack user_error_handlers; zend_ptr_stack user_exception_handlers; zend_error_handling_t error_handling; zend_class_entry *exception_class; /* timeout support */ int timeout_seconds; int lambda_count; HashTable *ini_directives; HashTable *modified_ini_directives; zend_objects_store objects_store; zval *exception, *prev_exception; zend_op *opline_before_exception; zend_op exception_op[3]; struct _zend_execute_data *current_execute_data; struct _zend_module_entry *current_module; zend_property_info std_property_info; zend_bool active; void *saved_fpu_cw; void *reserved[ZEND_MAX_RESERVED_RESOURCES]; };
我们可以通过宏EG来访问符号表, EG(active_symbol_table)访问当前作用域的符号变量表,EG(symbol_table)访问全局的变量符号表.
<?php $foo = 'bar'; ?>
上面这段代码很简单,创建变量foo,并赋值bar.之后的php代码中可以调用变量$foo。
看一下变量$foo,在php内核中是怎么实现的.
zval* foo; MAKE_STD_ZVAL(foo); //创建一个zval结构,并设置类型。 ZVAL_STRING(foo, "bar", 1); //赋值 ZEND_SET_SYMBOL( EG(active_symbol_table), "foo", foo); //将其加入当前作用域符号表,只有这样用户才能在PHP里使用这个变量。
通过简单的这三步,即可实现定义PHP变量。简单的原因,在于内核为我们提供了强大的宏。现在我们将宏分别展开。
#define MAKE_STD_ZVAL(zv) ALLOC_ZVAL(zv); INIT_PZVAL(zv) #define ALLOC_ZVAL(z) ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST) #define ZEND_FAST_ALLOC(p, type, fc_type) (p) = (type *) emalloc(sizeof(type)) #define INIT_PZVAL(z) (z)->refcount__gc = 1;(z)->is_ref__gc = 0;
MAKE_STD_ZVAL(foo)展开后得到:
(foo) = (zval *) emalloc(sizeof(zval)); (foo)->refcount__gc = 1; //引用次数 (foo)->is_ref__gc = 0; //是否被引用
可以看出,MAKE_STD_ZVAL做了三件事:分配内存、初始化zval结构中的refcount、is_ref.
ZVAL_STRING用到的宏:
#define ZVAL_STRING(z, s, duplicate) { \ const char *__s=(s); \ Z_STRLEN_P(z) = strlen(__s); \ Z_STRVAL_P(z) = (duplicate?estrndup(__s, Z_STRLEN_P(z)):(char*)__s);\ Z_TYPE_P(z) = IS_STRING; \ } #define Z_STRLEN_P(zval_p) Z_STRLEN(*zval_p) #define Z_STRLEN(zval) (zval).value.str.len #define Z_STRVAL_P(zval_p) Z_STRVAL(*zval_p) #define Z_STRVAL(zval) (zval).value.str.val #define Z_TYPE_P(zval_p) Z_TYPE(*zval_p) #define Z_TYPE(zval) (zval).type #define IS_STRING 6
宏展开后的代码:
const char *__s=("foo"); (foo).value.str.len=strlen(__s); (foo).value.str.val=(duplicate?estrndup(__s, (zval).value.str.len):(char*)__s); (foo).type=6;
ZVAL_STRING的主要功能就是:设置数据类型和赋值。
ZEND_SET_SYMBOL使用到的一些宏:
# define EG(v) (executor_globals.v)
展开后的代码:
ZEND_SET_SYMBOL(executor_globals.active_symbol_table, "foo", foo);
将变量名入当前作用域符号表。
下面我来看一下zval的定义:
zval在Zend/zend.h中被定义,ypedef struct _zval_struct zval; //原来它是 _zval_struct 的别名
typedef union _zvalue_value { long lval; //保存long类型的数据 double dval; //保存 double类型的数据 struct { char *val; //真正的值在这里 int len; //这里返回长度 } str; HashTable *ht; //数组等 zend_object_value obj; //这是一个对象 } zvalue_value; struct _zval_struct { zvalue_value value; //保存的值 zend_uint refcount__gc;//被引用的次数 如果为1 则只被自己使用如果大于1 则被其他变量以&的形式引用. zend_uchar type; //数据类型 这也是 为什么 PHP是弱类型的原因 zend_uchar is_ref__gc; //表示是否为引用 };
我们也可以通过写php扩展的方式来展示一下:
PHP_FUNCTION( test_test ){ zval *value; char *s="create a php variable";
//MAKE_STD_ZVAL value=(zval*)malloc(sizeof(zval)); memset(value,0,sizeof(value)); //ZVAL_STRING
value->is_ref__gc=0; //非引用变量 value->refcount__gc=1;//引用次数 只有自己 value->type=IS_STRING;//类型为字符串 value->value.str.val=s;//值 value->value.str.len=strlen(s);//长度 ZEND_SET_SYMBOL(EG(active_symbol_table),"a",value); }
在这里我们创建了一个php的变量$a.
下面来讲一下php弱类型变量的实现.
Zend/zend_type.h
25 typedef unsigned char zend_bool; 26 typedef unsigned char zend_uchar; 27 typedef unsigned int zend_uint; 28 typedef unsigned long zend_ulong; 29 typedef unsigned short zend_ushort;
根据zval的结构,可以看到_zvalue_value是真正保存数据的关键部分。通过共用体实现的弱类型变量声明。
Zend引擎是如何判别、存储PHP中的多种数据类型的呢?
_zval_struct.type中存储着一个变量的真正类型,根据type来选择如何获取zvalue_value的值
type值列表(Zend/zend.h): #define IS_NULL 0 #define IS_LONG 1 #define IS_DOUBLE 2 #define IS_BOOL 3 #define IS_ARRAY 4 #define IS_OBJECT 5 #define IS_STRING 6 #define IS_RESOURCE 7 #define IS_CONSTANT 8 #define IS_CONSTANT_ARRAY 9
来看一个简单的列子:
<?php $a = 1; //此时zval.type = IS_LONG,那么zval.value就去取lval. $a = array(); //此时zval.type = IS_ARRAY,那么zval.value就去取ht.
这其中最复杂的,并且在开发第三方扩展中经常需要用到的是"资源类型".
在PHP中,任何不属于PHP的内建的变量类型的变量,都会被看作资源来进行保存。
比如:数据库句柄、打开的文件句柄、打开的socket句柄。
资源类型,需要使用ZE提供的API函数来注册,资源变量的声明和使用将在单独的篇目中进行详细介绍。
正是因为ZE这样的处理方式,使PHP就实现了弱类型,而对于ZE的来说,它所面对的永远都是同一种类型zval。