PHP的底层分析
PHP编译
编译型语言:
对于C语言,C++, 编译成机器码(二进制)来运行.
java语言, 把.java编译成 .class,称为bytecode, 由jvm来运行.
解释语言:
解释器解释执行. 典型的如 linux shell.
解释器逐行来执行命令.
PHP稍有特殊之处,虽然是一个脚本语言,但不是靠解释器解释.
而是 zend 虚拟机,屏蔽了操作系统的区别.
php代码编译成opcode, 由zend虚拟机来执行opcode.
但是---opcode, PHP脚本一结束,opcode就清除了.
思考:opcode能否缓存?
PHP本身不支持,但是apc,xcache等加速器,实现了这样的效果.
我们解压PHP的源码包, 其中,
最核心的---Zend目录, 这是zend虚拟的实现. 包括栈,数据类型,编译器等,都在这实现.
最主要的main --PHP的一些内建函数,最主要函数都在这里放着.
最大的一个目录 ext -- PHP的扩展.
PHP的大部分功能,都是以extenstion形式来完成的.
PHP变量
PHP底层用C语言来实现的,C语言是强类型,而PHP是弱类型语言.是如何实现的呢?
通过Zend/zend.h我们可以看到:
PHP变量是通过zval结构体来存储的;_zval_struct
PHP变量的值是放在zval结构体中的value段中的 _zvalue_value
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;
zend_ast *ast;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* 变量的值,是zvalue联合体 */
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
常见的析构函数是zval_ptr_dtor。zval_ptr_dtor会减少zval的引用数量,
如果它遇到refcount=0,它会销毁和释放zval变量。
疑问:PHP中有8种数据类型,为什么zval->value 联合体中,只有5种?
答:
1: NULL,直接 zval->type = IS_NULL,就可以表示,不必设置 value的值.
2: BOOL型 , zval->type = IS_BOOL, 再设置 zval.value.lval = 1/0;
3: Resourc型 ,资源型 往往是服务器上打开的一个接口,如果 文件读取接口.
zval->type = IS_RESOURCE, zval->type.lval = 服务器上打开的接口的编号
创建变量的过程:
1.创建zval结构,并设置其类型;2.设置其值;3.将其变量名加入符号表
发现:PHP中,字符串类型的长度是已经缓存的,调用strlen时,系统可以直接返回其长度,不必计算.
PHP数组
PHP符号表
符号表是一张哈希表,里面存储了变量名->变量的zval结构体的地址
在zend/zend_globals.h 中
struct _zend_executor_globals {
...
HashTable *active_symbol_table; /*活动符号表*/
HashTable symbol_table; /* 全局符号表 */
...
}
PHP函数
在 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;
...
};
当执行到函数时,会生成函数的"执行环境结构体",包含函数名,参数,执行步骤,所在的类(如果是方法),
以及为这个函数生成一个符号表.符号表统一放在栈上.并把active_symbol_table指向刚产生的符号表,
active_symbol_table始终指向栈顶活动的表。
PHP中的局部变量和全局变量是如何实现的?
对于一个请求,任意时刻PHP都可以看到两个符号表(symbol_table和active_symbol_table),
其中前者用来维护全局变量。后者是一个指针,指向当前活动的变量符号表,
当程序进入到某个函数中时,zend就会为它分配一个符号表,同时将active_symbol_table指向此表。
通过这样的方式实现全局、局部变量的区分。
函数中使用全局变量:在函数中,我们可以通过显式申明global来使用全局变量。
在active_symbol_table中创建symbol_table中同名变量的引用,如果symbol_table中没有同名变量则会先创建。
PHP函数中静态变量
struct _zend_op_array {
...
/* static variables support */
HashTable *static_variables;
...
}
函数中静态变量的实现:函数分析完成生成1份op_array不管调用几次,
而op_array里的static_variables指向1个私有变量表:静态变量名称->地址
PHP常量-常量结构体
常量结构体 Zend/constants.h
typedef struct _zend_constant {
zval value; //变量结构体
int flags; //标志,是否大小写敏感等
char *name; //常量名
uint name_len; //常量名长度
int module_number; //模块名
} zend_constant;
常量的生成:
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)
...
}
常量的哈希表只有一个EG(zend_constants),是全局有效的.
PHP 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;
}
1.常量并没有检测名字:define('^_^',"laugh"),但是引用时就语法出问题;
2.PHP5.6起CONST定义类常量可以使用常量标量表达式,eg:const PER_DAY = 60 60 24;
3.define和CONST的区别是define可以用于定义全局常量,而CONST是定义类的常量
4.static静态变量与define,CONST的区别是static定义的变量是可以改变的,而后两者不行,
并且static静态变量是随类直接在内存中初始化,可以直接用,如$oneclass::hobby
5.在PHP5.6之前define定义数组是不行的,但是可以通过serialize把数组序列化,
PHP5.6之后可以直接const定义一个数组,如果是PHP7,可以直接用define定义数组
PHP对象
对象实例化后,zvalue联合体为对象标识符handle
/*{
value: {handle:2385}-->handle指向哈希表{name:'lily',age:5}
type: IS_OBJECT
recount_gc : 1
is_ref_gc :0
}*/
typedef unsigned int zend_object_handle;
typedef struct _zend_object_handlers zend_object_handlers;
typedef struct _zend_object_value {
zend_object_handle handle; //无符号整数
zend_object_handlers *handlers;
} zend_object_value;