一、PHP底层原理--知其然知其所以然,先看一张图
1、我们写的PHP代码是不能直接运行的,它首先经过词法分析器-语法分析器和编译器
这里你可能会觉得,PHP怎么会是编译性语言呢?一般认为PHP是脚本语言,是的。但严格的说来,PHP也是一种编译语言,它会将PHP编译为opcode的一个中间语言,有点像JAVA中的class文件。
2、生成cpcode之后再由zend公司开发的执行引擎执行
我们来做一个比较
C、C++语言 | JAVA |
编译成机器码(二进制)来运行 | 先把JAVA文件编译成.class,被称为bytecode,然后再用jvm来运行 所以JAVA是不可以跨平台的,而是jvm是跨平台的 |
解释语言 | PHP |
解释器解释执行,最经典的如:linux shell 解释器逐条来执行命令 | PHP有特殊,虽然它是一个脚本语言,但不是靠解释器解释执行。 而是zend虚拟机屏蔽了操作系统 PHP代码编译成opcode.由虚拟机来执行opcode 但是--opcode,PHP脚本一结束,opcode就清除了(java可以将.class打包发布) |
3、那么opcode是否可以缓存呢?
PHP本身不支持,但是apc,xcache等加速器实现了这样的效果(如果一生成就扔掉就太浪费了)我们看看一个总结
zend编译器(PHP->opcode) |
zend虚拟机(执行opcode) |
操作系统层面 win/linux/mac |
二、PHP变量的底层实现
PHP底层用C语言来实现的,C语言是强类型,而PHP是弱类型语言(如必须加上变量类型)。那么PHP是如何实现的呢?
我们解压PHP的源代码包,看到如下的目录:如图所示
我们来分析
我们打开Zend文件,然后找到zend.h文件
找到
typedef struct _zval_struct zval 的结构体
再看看它的实现
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
}
PHP变量实现的核心就在上面的几行代码之中
PHP变量的值就是用上面的结构体来描述的
它由四个字段组成
分析第一个,zvalue_value value;
typedef union _zvalue_value { // 联合(枚举)
long lval;
double dval;
struct {
char *val;
int len;
};
HashTable *ht;
zend_object_value obj;
} zvalue_value;
分析第二个,zend_uchar type;
它有可能是IS_NULL, IS_BOOL
所以判断一个变量的类型是什么,是根据zend_uchar type来决定的
它的值是多少是通过typedef union _zvalue_value它的联合体来决定的
我们来分析一段代码,看看PHP中的变量是怎么实现的
$a = 3;
/***
一个结构体产生了
{
union_value {long 3} // 描述值
type IS_LONG // 描述类型
refcount_gc:1
is_ref_gc:0
}
***/
type字段的值以常量形式存在
我们看看它具体保存的是什么
IS_NULL, IS_BOOL, IS_LONG, IS_DOUBLE, IS_STRING, IS_ARRARY. IS_OBJECT, IS_RESOURCE
它其实对应了PHP数组中的八种数据类型
这里产生了一个问题,在zvalue_value中只提供了五中数据类型,下面是三种没有对应上的类型
1: NULL直接对应zval->type=IS_NULL,就可以表示不必设置value的置 |
2: BOOL型,zval->type=IS_BOOL,再设置zval_value.lval = 1/0 |
3: Resource型,资源型往往是服务器上打开的一个接口,如果文件读取接口 zval->type=IS_RESOURCE, zval->type.lval=服务器打开的接口的编号 |
PHP中,字符串类型,长度是已经缓存的,调用strlen不用计算,速度非常快;长度是直接放入其结构体中
$b = 'hello';
/**
{
{
char:'hello';
len:5;
}
type: IS_STRING;
refcount__gc:1
is_ref__gc:0
}
**/
再问一个问题,变量的名放在哪里的
在PHP中,变量的名是放在符号表中的symbol_table
这里要闻,符号表是什么?
符号表就是一张哈希表,里面存储了变量名->变量的zval结构体的地址
Zend/zend_globals.h找到HashTable synbol_table; /* main synbol table */
哈希表我们就可以把它作为一个关联数组
看一段代码,看见声明一个变量,就必须要想到的事情
$a = 5;
$b = 5.54321;
$c = 'hello';
/**
生成了3个结构体
同时,全局富豪表中,多了3条记录
a--> 0x123 --> 结构体 {5}
b--> 0x213 --> 结构体 {5.54321}
c--> 0x339 --> 结构体 {hello}
**/
上面是全局的符号表,一般由全局的必定有局部的符号表
HashTable *active_synbol_table
原理都是一样的
我们再看看变量的赋值与引用,先看如下代码:
$a = 3;
$b = $a
在底层是否生成了2个结构体呢?
是的,在PHP实现的过程中并没有生成一个新的结构体
$a = 3
/**
zvalue:3
type:IS_LONG
refount__gc=1
is_ref_gc:0
**/
$b = $a
/**
zvalue:3
type:IS_LONG
refcount__gc=2 这里发生变化
is_ref_gc:0
<pre name="code" class="php">它们在地址上都指向同一个结构体,节省空间,并没有发生结构体的复制
**/
123