前言
工作+实习快一年了,搞php后端开发,一直很迷茫怎么提高自己,就先从php源码开始吧,本人比较菜,本文章写的比较赶时间,所以有什么错误或者漏掉的地方,望各位大神指正,多交流才能成长嘛,嘿嘿。
本文主要是针对php7,php5的话可以移步到庆哥的博客看,还有就是小菜我读的是《php7内核剖析》这本书。
接下来我会使用到xdebug来调试php源码
本文有参照ohmygirl博客中的部分内容以及代码。
本文所用环境为windows,php7.0.10
php7中zval,zend_value的基本结构
php7和php5不同的地方有很多,zval,zend_value结构就是其中之一
在php7中
zval定义在zend_types.h中
在zval这个结构体重包含三个部分 zend_value(存储实际的内容),u1,u2两个联合体,其中u1主要存储变量相关的一下属性,而u2则是对u1的一些补充,例如当用到数组的时候,会用到u2.next来解决key哈希后出现的hash冲突
struct _zval_struct {
zend_value value; /* 存储变量的实际内容 */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /* 存储变量的类型 */
zend_uchar type_flags, /* 用于标识变量状态,例如GC方面的管理,通过设置为IS_TYPE_COLLECTABLE 则变量会被收集到GC中回收垃圾的buffer缓存区中 */
zend_uchar const_flags,
zend_uchar reserved) /* call info for EX(This) */
} v;
uint32_t type_info;
} u1;
union {
uint32_t next; /* hash collision chain */
uint32_t cache_slot; /* literal cache slot */
uint32_t lineno; /* line number (for ast nodes) */
uint32_t num_args; /* arguments number for EX(This) */
uint32_t fe_pos; /* foreach position */
uint32_t fe_iter_idx; /* foreach iterator index */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;
};
zend_uchar type: 以下为外部使用的变量类型
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
php7中zend_value结构
typedef union _zend_value {
zend_long lval; /* long value */
double dval; /* double value */
zend_refcounted *counted; /*用于统计计数用,*/
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
这里我们先解释一下php7的zval,zend_valu中重要的的几个变量
zval中:
(1)zend_uchar type 这个是用来表示当前变量(例如 $a)是什么类型的变量($a是string类型?还是int类型?还是引用类型....)
zend_value中:
(1)zend_refcounted *counted; 表示引用计数的次数,
何为引用计数?
就是zend_value变量被zval引用的次数,例如我们$a=“abcs”;$c=$a;$b=$a;此时counted=3,如下图,其中refcount也是实现GC自动内存回收的基础,下面会详细讲解
php7变量的内部实现
php7中对与变量的实现分以下几种方式
(1).对于boolen类型,还有null,undefined,这种没有具体值,只有类型的类型,直接在zval中通过zend_uchar type的类型来判断,无需通过引用计数来实现。
正是因为没有通过应用计数来实现,所以它refcount为0
(2)对于int类型和float类型,因为在zend_value中有zend_long和double来保存数据,如下图,所以,在赋值的时候就不需要再使用引用计数了,在拷贝的直接进行赋值就行了,这样做可以省掉大量引用计数的相关操作
定义一个$a=1,在php内核中zval和zend_value的关系
(3)第三种就是常规的使用引用计数的方式来进行来进行变量的定义。在这些变量的实现中,都是通过指针指向一个具体的数据类型
例如 :
zend_string *str;
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
$假设我定义一个$a="111";其内部的实现就是,注意这里zend_string中char val[1],主要是因为C语言中字符串是以“0”结尾的,所以在zend_string中$a="111"这个变量值的存储是char val[4]="111"。
php7中赋值
1.普通赋值
前面说到,在php中,定义一个变量$a="444",实际上是生成了一个zval,和一个zend_value,然后zval指向这个zend_value来实现对$a="444"的定义的,然后通过refcount来统计引用的次数。
**所以可以总结出,refcount表示当前有多少个zval指向同一个zend_value**
我们定义如下:
$a="111";
$b=$a;
当然对于像boole型还有int,double,null变量,他们的直接通过zval保存,不会公用一个zend_value,所以直接使用深拷贝。
至于什么是深拷贝,什么是浅拷贝,最直接的区别就是在于有没有重新生成一个一模一样的zend_value,详细请参看深拷贝和浅拷贝。
后面的写时赋值(COW copy on write)就会使用到深拷贝。
对于php的赋值,实际上并不是所有的类型都是一样的,刚刚也有说到,在php的zval中就有一个专门的字段用于标识当前类型适合哪种形式的那就是。
zend_uchar type_flags,
| refcounted | collectable | copyable | immutable
----------------+------------+-------------+----------+----------
simple types | | | |
string | x | | x |
interned string | | | |
array | x | x | x |
immutable array | | | | x
object | x | x | |
resource | x | | |
reference | x | | |
zend_uchar type_flags这个字段用于标识当前的zval的属于哪种赋值方式或者处于哪种状态,主要是用于内存方面的管理具体参见内存管理,
2.引用赋值
php中的引用赋值就是我们常用的引用,例如$a=&$b,这样。
在php7中实现引用的时候,在php中实现引用的时候,会先生成一个zend_reference类型,这个类型中嵌套一个zval,然后这个zval的zend_value会指向之前的之前的那个zval的zend_value,原来的那个zval类型转换成IS_REFERENCE类型,简单来说**就是将原有的zval类型转换成IS_REFERENCE,并新生成zend_reference类型指向原zend的zend_value。(好jb绕,理了好久)。
struct _zend_reference {
zend_refcounted_h gc;
zval val;
};
例如我们定义一个$a="1111";$b=&$a;它的变换如下图
$a="111"
$b=&$a;
可能有朋友注意到了,在zend_reference中refcount为2 ,但是在最后的真实的zend_value为refcount为1,这是为什么呢,其实很好理解,前面说过,refcount表示有多少个zval指向该zend_value,zend_reference中只有一个zval指向了zend_value。
中间加一层zend_reference这样做其实有很多好处,这样可以保留原有的zend_value类型不变,为深拷贝操作提供拷贝条件,下面我们举个例子就知道了
是不是一目了然,只能说开发内核的那些神牛太牛逼了。。。。。。。
当然关于php中值传递不仅仅那么简单,还有很多很复杂的东西,小菜我也是才看没多久,望各位大牛勿喷
php中的COW (copy on write)
写时复制是一种很重要的优化手段,这里涉及到深拷贝和浅拷贝的知识,请移步。
关于深拷贝,刚刚上面的这个例子就是很好的说明
所谓深拷贝就是将原有的数据拷贝一份放到独立分配一个地址空间。
而写时赋值就是对深拷贝的一种优化吧,意思是只有当发生写操作的时候才进行深拷贝
举个例子:
$a="3333";
$b=$a;
$b.="444";
$a="3333";
$b=$a;
这个操作会使两个zval指向同一个zend_value
这里并没有触发COW,执行深拷贝
当$b.="444";发生了写操作的时候,触发COW,执行深拷贝,拷贝了完全一样的一份zend_value,$b所在的zval由原来的和$a所在的zval共同指向之前的zend_value, 转换成指向拷贝出的新的zend_value
如果不进行深拷贝的话,那么当执行$b.="444";后,$a也会等于“3333444”
当然不是所有的zend_value类型都可以进行复制,这个请参见我之前的那个zend_uchar type_flags表
**文章中可能有些不足的地方,恳求指正。
本来打算再写写GC回收的原理的,但是现在已经2点多了,23333333.明天还要继续打码呢。。。。。。。不好意思**
下一篇准备写一下php中的数组的实现;
lift needs art,i need girl
本文参考
http://bbs.csdn.net/topics/39...
http://blog.csdn.net/black_ox...
http://blog.csdn.net/xiaolei1...
https://segmentfault.com/a/11...
https://github.com/laruence/p...
https://www.cnblogs.com/ohmyg...
可能有遗漏的参考博客,望博主见谅
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。