N首页> 云计算

【问底】王帅:深入PHP内核(一)——弱类型变量原理探究

发表于2014-09-19 09:00| 13055次阅读| 来源CSDN| 36 条评论| 作者王帅
摘要:PHP作为一门简单而强大的语言,能够提供很多Web适用的语言特性,而从本期《问底》开始,王帅将从实践出发,带你弄清PHP内核中一些常用的部分,比如这里的“弱类型变量原理”。

PHP是一门简单而强大的语言,提供了很多Web适用的语言特性,其中就包括了变量弱类型,在弱类型机制下,你能够给一个变量赋任意类型的值。

PHP的执行是通过Zend Engine(下面简称ZE),ZE是使用C编写,在底层实现了一套弱类型机制。ZE的内存管理使用写时拷贝、引用计数等优化策略,减少再变量赋值时候的内存拷贝。

下面不光带你探索PHP弱类型的原理,也会在写PHP扩展角度,介绍如何操作PHP的变量。

1. PHP的变量类型

PHP的变量类型有8种:

  • 标准类型:布尔boolen,整型integer,浮点float,字符string
  • 复杂类型:数组array,对象object
  • 特殊类型:资源resource

PHP不会严格检验变量类型,变量可以不显示的声明其类型,而在运行期间直接赋值。也可以将变量自由的转换类型。如下例,没有实现声明的情况下,$i可以赋任意类型的值。

  1. <? php  $i = 1;   //int $i = 'show me the money';  //string $i = 0.02;  // float $i = array(1, 2, 3);  // array $i = new Exception('test', 123); // object $i = fopen('/tmp/aaa.txt', 'a') // resource ?>

如果你对弱类型原理理解不深刻,在变量比较时候,会出现“超出预期”的惊喜。

  1. <? PHP $str1 = null;  $str2 = false;  echo $str1==$str2 ? '相等' : '不相等';  $str3 = '';  $str4 = 0;  echo $str3==$str4 ? '相等' : '不相等';  $str5 = 0;  $str6 = '0';  echo $str5==$str6 ? '相等' : '不相等';  ?>

以上三个结果全部是相等,因为在变量比较的时候,PHP内部做了变量转换。如果希望值和类型同时判断,请使用三个=(如,$a===0)来判断。也许你会觉得司空见惯,也许你会觉得很神奇,那么请跟我一起深入PHP内核,探索PHP变量原理。

2. 变量的存储及标准类型介绍

PHP的所有变量,都是以结构体zval来实现,在Zend/zend.h中我们能看到zval的定义:

  1. typedef union _zvalue_value {     long lval;                 /* long value */     double dval;               /* double value */     struct {                            char *val;         int len;               /* this will always be set for strings */     } str;                     /* string (always has length) */     HashTable *ht;             /* an array */     zend_object_value obj;     /* stores an object store handle, and handlers */  } zvalue_value;
属性名 含义 默认值
refcount__gc 表示引用计数 1
is_ref__gc 表示是否为引用 0
value 存储变量的值  
type 变量具体的类型  

其中refcount__gc和is_ref__gc表示变量是否是一个引用。type字段标识变量的类型,type的值可以
是:IS_NULL,IS_BOOL,IS_LONG,IS_FLOAT,IS_STRING,IS_ARRAY,IS_OBJECT,IS_RESOURCE。
PHP根据type的类型,来选择如何存储到zvalue_value。

zvalue_value能够实现变量弱类型的核心,定义如下:

  1. typedef union _zvalue_value {     long lval;                 /* long value */     double dval;               /* double value */     struct {                            char *val;         int len;               /* this will always be set for strings */     } str;                     /* string (always has length) */     HashTable *ht;             /* an array */     zend_object_value obj;     /* stores an object store handle, and handlers */  } zvalue_value;

布尔型,zval.type=IS_BOOL,会读取zval.value.lval字段,值为1/0。如果是字符串,zval.type=IS_STRING,会读取zval.value.str,这是一个结构体,存储了字符串指针和长度。

C语言中,用"\0"作为字符串结束符。也就是说一个字符串"Hello\0World"在C语言中,用printf来输出的话,只能输出
hello,因为"\0"会认为字符已经结束。PHP中是通过结构体的_zval_value.str.len来控制字符串长度,相关函数不会遇到"
\0"结束。所以PHP的字符串是二进制安全的。

如果是NULL,只需要zval.type=IS_NULL,不需要读取值。

通过对zval的封装,PHP实现了弱类型,对于ZE来说,通过zval可以存取任何类型。

3. 高级类型Array和Object数组Array

数组是PHP语言中非常强大的一个数据结构,分为索引数组和关联数组,zval.type=IS_ARRAY。在关联数组中每个key可以存储任意类型的数据。PHP的数组是用Hash
Table实现的,数组的值存在zval.value.ht中。

后面会专门讲到PHP哈希表的实现。

对象类型的zval.type=IS_OBJECT,值存在zval.value.obj中。

4. 特殊类型——资源类型(Resource)介绍

资源类型是个很特殊的类型,zval.type=IS_RESOURCE,在PHP中有一些很难用常规类型描述的数据结构,比如文件句柄,对于C语
言来说是一个指针,不过PHP中没有指针的概念,也不能用常规类型来约束,因此PHP通过资源类型概念,把C语言中类似文件指针的变量,用zval结构来
封装。资源类型值是一个整数,ZE会根据这个值去资源的哈希表中获取。

资源类型的定义:

  1. typedefstruct_zend_rsrc_list_entry {     void *ptr;     int type;     int refcount;  }zend_rsrc_list_entry;

其中,ptr是一个指向资源的最终实现的指针,例如一个文件句柄,或者一个数据库连接结构。type是一个类型标记,用于区分不同的资源类型。refcount用于资源的引用计数。

内核中,资源类型是通过函数ZEND_FETCH_RESOURCE获取的。

  1. ZEND_FETCH_RESOURCE(con, type, zval *, default, resource_name, resource_type);

5. 变量类型的转换

按照现在我们对PHP语言的了解,变量的类型依赖于zval.type字段指示,变量的内容按照zval.type存储到zval.value。当
PHP中需要变量的时候,只需要两个步骤:把zval.value的值或指针改变,再改变zval.type的类型。不过对于PHP的一些高级变量
Array/Object/Resource,变量转换要进行更多操作。

变量转换原理分为3种:

5.1 标准类型相互转换

比较简单,按照上述的步骤转化即可。

5.2 标准类型与资源类型转换

资源类型可以理解为是int,比较方便转换标准类型。转换后资源会被close或回收。

  1. <? php $var = fopen('/tmp/aaa.txt', 'a'); // 资源 #1 $var = (int) $var; var_dump($var);  // 输出1 ?>

5.3 标准类型与复杂类型转换

Array转换整型int/浮点型float会返回元素个数;转换bool返回Array中是否有元素;转换成string返回'Array',并抛出warning。

详细内容取决于经验,请阅读PHP手册:

http://php.net/manual/en/language.types.type-juggling.php

5.4 复杂类型相互转换

array和object可以互转。如果其它任何类型的值被转换成对象,将会创建一个内置类stdClass的实例。

在我们写PHP扩展的时候,PHP内核提供了一组函数用于类型转换:

void convert_to_long(zval* pzval)
void convert_to_double(zval* pzval)
void convert_to_long_base(zval* pzval, int base)
void convert_to_null(zval* pzval)
void convert_to_boolean(zval* pzval)
void convert_to_array(zval* pzval)
void convert_to_object(zval* pzval)
void convert_object_to_type(zval* pzval, convert_func_t converter)

PHP内核提供的一组宏来方便的访问zval,用于更细粒度的获取zval的值:

内核访问zval容器的API
访问变量
Z_LVAL(zval)

(zval).value.lval
Z_DVAL(zval)

(zval).value.dval
Z_STRVAL(zval)

(zval).value.str.val
Z_STRLEN(zval)

(zval).value.str.len
Z_ARRVAL(zval)

(zval).
value.ht
Z_TYPE(zval)

(zval).type
Z_LVAL_P(zval)

(*zval).value.lval
Z_DVAL_P(zval)

(*zval).value.dval
Z_STRVAL_P(zval_p)

(*zval).value.str.val
Z_STRLEN_P(zval_p)

(*zval).value.str.len
Z_ARRVAL_P(zval_p)

(*zval).
value.ht
Z_OBJ_HT_P(zval_p)

(*zval).value.obj.handlers
Z_LVAL_PP(zval_pp)

(**zval).value.lval
Z_DVAL_PP(zval_pp)

(**zval).value.dval
Z_STRVAL_PP(zval_pp)

(**zval).value.str.val
Z_STRLEN_PP(zval_pp)

(**zval).value.str.len
Z_ARRVAL_PP(zval_pp)

(**zval).
value.ht

6. 变量的符号表与作用域

PHP的变量符号表与zval值的映射,是通过HashTable(哈希表,又叫做散列表,下面简称HT),HashTable在ZE中广泛使用,包括常量、变量、函数等语言特性都是HT来组织,在PHP的数组类型也是通过HashTable来实现。

举个例子:

  1. <? php $var = 'Hello World'; ?>

$var的变量名会存储在变量符号表中,代表$var的类型和值的zval结构存储在哈希表中。内核通过变量符号表与zval地址的哈希映射,来实现PHP变量的存取。

为什么要提作用域呢?因为函数内部变量保护。按照作用域PHP的变量分为全局变量和局部变量,每种作用域PHP都会维护一个符号表的
HashTable。当在PHP中创建一个函数或类的时候,ZE会创建一个新的符号表,表明函数或类中的变量是局部变量,这样就实现了局部变量的保护--
外部无法访问函数内部的变量。当创建一个PHP变量的时候,ZE会分配一个zval,并设置相应type和初始值,把这个变量加入当前作用域的符号表,这
样用户才能使用这个变量。

内核中使用ZEND_SET_SYMBOL来设置变量:

  1. ZEND_SET_SYMBOL( EG(active_symbol_table), "foo", foo);

查看_zend_executor_globals结构

  1. Zend/zend_globals.h
  2. struct _zend_executor_globals {          //略        HashTable symbol_table;//全局变量的符号表        HashTable *active_symbol_table;//局部变量的符号表        //略  };

在写PHP扩展时候,可以通过EG宏来访问PHP的变量符号表。EG(symbol_table)访问全局作用域的变量符号
表,EG(active_symbol_table)访问当前作用域的变量符号表,局部变量存储的是指针,在对HashTable进行操作的时候传递给相
应函数。

为了更好的理解变量的哈希表与作用域,举个简单的例子:

  1. <? php $temp = 'global'; function test() {     $temp = 'active'; } test(); var_dump($temp); ?>

创建函数外的变量$temp,会把这个它加入全局符号表,同时在全局符号表的HashTable中,分配一个字符类型的zval,值为‘global‘。创建函数test内部变量$temp,会把它加入属于函数test的符号表,分配字符型zval,值为’active'

7. PHP扩展中变量操作

创建PHP变量

我们可以在扩展中调用函数MAKE_STD_ZVAL(pzv)来创建一个PHP可调用的变量,MAKE_STD_ZVAL应用到的宏有:

  1. #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)展开后得到:

  1. (foo) = (zval *) emalloc(sizeof(zval));   (foo)->refcount__gc = 1;   (foo)->is_ref__gc = 0;

可以看出,MAKE_STD_ZVAL做了三件事:分配内存、初始化zval结构中的refcount、is_ref。

内核中提供一些宏来简化我们的操作,可以只用一步便设置好zval的类型和值。

API Macros for Accessing zval 
实现方法
ZVAL_NULL(pvz)

Z_TYPE_P(pzv) = IS_NULL
ZVAL_BOOL(pvz)

Z_TYPE_P(pzv) = IS_BOOL;

Z_BVAL_P(pzv) = b ? 1 : 0;
ZVAL_TRUE(pvz)

ZVAL_BOOL(pzv, 1);
ZVAL_FALSE(pvz)

ZVAL_BOOL(pzv, 0);
ZVAL_LONG(pvz, l)(l 是值)

Z_TYPE_P(pzv) = IS_LONG;Z_LVAL_P(pzv) = l;
ZVAL_DOUBLE(pvz, d)

Z_TYPE_P(pzv) = IS_DOUBLE;Z_LVAL_P(pzv) = d;
ZVAL_STRINGL(pvz, str, len, dup)

Z_TYPE_P(pzv) = IS_STRING;Z_STRLEN_P(pzv) = len;

if (dup) {

    {Z_STRVAL_P(pzv) =estrndup(str, len + 1);} 

}else {

    {Z_STRVAL_P(pzv) = str;}

}
ZVAL_STRING(pvz,
str, len)

ZVAL_STRINGL(pzv, str,strlen(str), dup);
ZVAL_RESOURCE(pvz,
res)

Z_TYPE_P(pzv) = IS_RESOURCE;Z_RESVAL_P(pzv) = res; 


ZVAL_STRINGL(pzv,str,len,dup)中的dup参数

先阐述一下ZVAL_STRINGL(pzv,str,len,dup); str和len两个参数很好理解,因为我们知道内核中保存了字符串的地址和它的长度,后面的dup的意思其实很简单,它指明了该字符串是否需要被复制。值为
1 将先申请一块新内存并赋值该字符串,然后把新内存的地址复制给pzv,为 0 时则是直接把str的地址赋值给zval。

ZVAL_STRINGL与ZVAL_STRING的区别

如果你想在某一位置截取该字符串或已经知道了这个字符串的长度,那么可以使用宏 ZVAL_STRINGL(zval, string, length,
duplicate) ,它显式的指定字符串长度,而不是使用strlen()。这个宏该字符串长度作为参数。但它是二进制安全的,而且速度也比ZVAL_STRING快,因为少了个strlen。


ZVAL_RESOURCE约等于ZVAL_LONG

在章节4中我们说过,PHP中的资源类型的值是一个整数,所以ZVAL_RESOURCE和ZVAL_LONG的工作差不多,只不过它会把zval的类型设置为
IS_RESOURCE。

8. 总结

PHP的弱类型是通过ZE的zval容器转换完成,通过哈希表来存储变量名和zval数据,在运行效率方面有一定牺牲。另外因为变量类型的隐性转换,在开发过程中对变量类型检测力度不够,可能会导致问题出现。

不过PHP的弱类型、数组、内存托管、扩展等语言特性,非常适合Web开发场景,开发效率很高,能够加快产品迭代周期。在海量服务中,通常瓶颈存在
于数据访问层,而不是语言本身。在实际使用PHP不仅担任逻辑层和展现层的任务,我们甚至用PHP开发的UDPServer/TCPServer作为数据
和cache的中间层。

弱类型变量原理探究(转载 http://www.csdn.net/article/2014-09-15/2821685-exploring-of-the-php)的更多相关文章

  1. 【问底】王帅:深入PHP内核(一)——弱类型变量原理探究

    来源:CSDN    http://www.csdn.net/article/2014-09-15/2821685-exploring-of-the-php 作者:王帅 摘要:PHP作为一门简单而强大 ...

  2. SpringBoot:运行原理探究

    西部开源-秦疆老师:基于SpringBoot 2.1.6 的博客教程 秦老师交流Q群号: 664386224 未授权禁止转载!编辑不易 , 转发请注明出处!防君子不防小人,共勉! SpringBoot ...

  3. go--->共享内存和通信两种并发模式原理探究

    共享内存和通信两种并发模式原理探究 并发理解 人类发明计算机编程的本质目的是为了什么呢?毫无疑问是为了解决人类社会中的各种负责业务场景问题.ok,有了这个出发点,那么想象一下,比如你既可以一心一意只做 ...

  4. [原] KVM 虚拟化原理探究(1)— overview

    KVM 虚拟化原理探究- overview 标签(空格分隔): KVM 写在前面的话 本文不介绍kvm和qemu的基本安装操作,希望读者具有一定的KVM实践经验.同时希望借此系列博客,能够对KVM底层 ...

  5. [原] KVM 虚拟化原理探究 —— 目录

    KVM 虚拟化原理探究 -- 目录 标签(空格分隔): KVM KVM 虚拟化原理探究(1)- overview KVM 虚拟化原理探究(2)- QEMU启动过程 KVM 虚拟化原理探究(3)- CP ...

  6. [原] KVM 虚拟化原理探究(6)— 块设备IO虚拟化

    KVM 虚拟化原理探究(6)- 块设备IO虚拟化 标签(空格分隔): KVM [toc] 块设备IO虚拟化简介 上一篇文章讲到了网络IO虚拟化,作为另外一个重要的虚拟化资源,块设备IO的虚拟化也是同样 ...

  7. [原] KVM 虚拟化原理探究(5)— 网络IO虚拟化

    KVM 虚拟化原理探究(5)- 网络IO虚拟化 标签(空格分隔): KVM IO 虚拟化简介 前面的文章介绍了KVM的启动过程,CPU虚拟化,内存虚拟化原理.作为一个完整的风诺依曼计算机系统,必然有输 ...

  8. [原] KVM 虚拟化原理探究(4)— 内存虚拟化

    KVM 虚拟化原理探究(4)- 内存虚拟化 标签(空格分隔): KVM 内存虚拟化简介 前一章介绍了CPU虚拟化的内容,这一章介绍一下KVM的内存虚拟化原理.可以说内存是除了CPU外最重要的组件,Gu ...

  9. [原] KVM 虚拟化原理探究(3)— CPU 虚拟化

    KVM 虚拟化原理探究(3)- CPU 虚拟化 标签(空格分隔): KVM [TOC] CPU 虚拟化简介 上一篇文章笼统的介绍了一个虚拟机的诞生过程,从demo中也可以看到,运行一个虚拟机再也不需要 ...

随机推荐

  1. ImageX用来做Windows OEM部署

    https://technet.microsoft.com/en-us/library/cc722145(v=ws.10).aspx http://download.csdn.net/user/phc ...

  2. 国家发改委发布的数据,前三季度我国生产的手机、PC、集成电路、宽带上网的数量

    集微网消息,根据国家发改委发布的数据,前三季度,我国生产集成电路944亿块,同比增长18.2%. 此外,前三季度,生产手机15亿部,同比增长17.6%,其中智能手机11亿部,增长12.1%,占全部手机 ...

  3. Mixing Delphi and C++(相互调用)

    Mixing Delphi and C++ You have a TStringList and <algorithm>. What can you do? Quite a lot, ac ...

  4. 【Android开发学习笔记】【第四课】基础控件的学习

    通过一个简单的例子来学习下面几种控件: 1.TextView:简单的文本显示控件 2.EditText:可以编辑的文本框 3.Button:按钮 4.Menu:这里指的是系统的Menu 5.Toast ...

  5. C++ 类成员函数作为参数

    #include <iostream> using namespace std; typedef int int32_t; struct IMsgBody{ int body; }; st ...

  6. zepto源码--classRE、maybeAddPx、children、defaultDisplay--学习笔记

    1.classRE 对获取className的操作,进行缓存.如果缓存中有,直接读取缓存中的值,如果没有,则先进行缓存的存储,再读取值. 利用前面变量定义的classCache={}进行缓存的操作,如 ...

  7. 一切从IL开始

    IL是什么? IL是Intermediate Language的缩写,是.Net代码转化成机器语言的一个中间语言,因此又把IL语言称之为反汇编语言. IL工具有哪些? 俗话说,工欲善其事必先利其器.了 ...

  8. WordPress显示备案号

    备案时,需要显示备案号,而wordpress默认模板本身不带这个信息,为了更快速应付备案,解决方案如下: 根据wp-config.php的提示 .......... /** * zh_CN本地化设置: ...

  9. 1046 A^B Mod C

    1046 A^B Mod C 基准时间限制:1 秒 空间限制:131072 KB 给出3个正整数A B C,求A^B Mod C. 例如,3 5 8,3^5 Mod 8 = 3. Input 3个正整 ...

  10. Truncate table

    Truncate是一个能够快速清空资料表内所有资料的SQL语法.并且能针对具有自动递增值的字段,做计数重置归零重新计算的作用.