PHP内核探索:变量的类型

PHP弱类型变量特性是如何实现?
服务器君一共花费了233.097 ms进行了5次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议

所有的编程语言都要提供一种数据的存储与检索机制,PHP也不例外。其它语言大都需要在使用变量之前先定义,并且它的类型也是无法再次改变的,而PHP却允许程序猿自由的使用变量而无须提前定义,甚至可以随时随意的对已存在的变量转换成其它任何PHP支持的数据类型。在程序在运行的时候,PHP还会自动的根据需求转换变量的类型。

如果你用过PHP,肯定体验过PHP的弱类型的变量体系。众所周知,PHP引擎是用C写的,而C确实一种强类型的编程语言,PHP内核中是如何用C来实现自己的这种弱类型特性的?下面谈谈变量的类型。

PHP在内核中是通过zval这个结构体来存储变量的,它的定义在Zend/zend.h文件里,简短精炼,只有四个成员组成:

struct _zval_struct {
	zvalue_value value;	/* 变量的值 */
	zend_uint refcount__gc;
	zend_uchar type;	/* 变量当前的数据类型 */
	zend_uchar is_ref__gc;
};
typedef struct _zval_struct zval;

//在Zend/zend_types.h里定义的:
typedef unsigned int zend_uint;
typedef unsigned char zend_uchar;

zval里的refcout__gc是zend_uint类型,也就是unsinged int型,is_ref__gc和type则是unsigned char型的。保存变量值的value则是zvalue_value类型(PHP5),它是一个Union,同样定义在了Zend/zend.h文件里:

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;
} zvalue_value;

在以上实现的基础上,PHP语言得以实现了8种数据类型,这些数据类型在内核中的分别对应于特定的常量,它们分别是:

常量名称: 
IS_NULL第一次使用的变量如果没有初始化过,则会自动的赋予这个变量,当然我们也可以在PHP语言中通过null这个常量来给予变量null类型的值。 这个类型的值只有一个 ,就是NULL,它和0与false是不同的。
IS_BOOL布尔类型的变量有两个值,true或者false。在PHP语言中,while、if等语句会自动的把表达式的值转成这个类型的。
IS_LONGPHP语言中的整型,在内核中是通过所在操作系统的singed long数据类型来表示的。在最常见的32位操作系统中,它可以存储从-2147483648 到 +2147483647范围内的任一整数。有一点需要注意的是,如果PHP语言中的整型变量超出最大值或者最小值,它并不会直接溢出,而是会被内核转换成IS_DOUBLE类型的值然后再参与计算。再者,因为使用了singed long来作为载体,所以这也就解释了为什么PHP语言中的整型数据都是带符号的了。 
$a=2147483647;
$a++;
echo $a;//会正确的输出	2147483648;	
IS_DOUBLEPHP中的浮点数据是通过C语言中的singed double型变量来存储的,这最终取决与所在操作系统的浮点型实现。 我们做为程序猿,应该知道计算机是无法精准的表示浮点数的,而是采用了科学计数法来保存某个精度的浮点数。用科学计数法,计算机只用8位便可以保存2.225x10^(-308)1.798x10^308之间的浮点数。用计算机来处理浮点数简直就是一场噩梦,十进制的0.5专成二进制是0.1,0.8转换后是0.1100110011....。但是当我们从二进制转换回来的时候,往往会发现并不能得到0.8。我们用1除以3这个例子来解释这个现象:1/3=0.3333333333.....,它是一个无限循环小数,但是计算机可能只能精确存储到0.333333,当我们再乘以三时,其实计算机计算的数是0.333333*3=0.999999,而不是我们平时数学中所期盼的1.0.
IS_STRINGPHP中最常用的数据类型——字符串,在内存中的存储和C差不多,就是一块能够放下这个变量所有字符的内存,并且在这个变量的zval实现里会保存着指向这块内存的指针。与C不同的是,PHP内核还同时在zval结构里保存着这个字符串的实际长度,这个设计使PHP可以在字符串中嵌入‘\0’字符,也使PHP的字符串是二进制安全的,可以安全的存储二进制数据!本着艰苦朴素的作风,内核只会为字符串申请它长度+1的内存,最后一个字节存储的是‘\0’字符,所以在不需要二进制安全操作的时候,我们可以像通常C语言的概念那样来使用它。
IS_ARRAY数组是一个非常特殊的数据类型,它唯一的功能就是包含别的变量。在C语言中,一个数组只能承载一种类型的数据,而PHP语言中的数组则灵活的多,它可以承载任意类型的数据,这一切都是HashTable的功劳,每个HashTable中的元素都有两部分组成:索引与值,每个元素的值都是一个独立的zval(确切的说应该是指向某个zval的指针)。
IS_OBJECT和数组一样,对象也是用来存储复合数据的,但是与数组不同的是,对象还需要保存以下信息:方法、访问权限、类常量以及其它的处理逻辑。相对与zend engine V1,V2中的对象实现已经被彻底修改,所以我们PHP扩展开发者如果需要自己的扩展支持面向对象的工作方式,则应该对PHP5和PHP4分别对待!
IS_RESOURCE有一些数据的内容可能无法直接呈现给PHP用户的,比如与某台mysql服务器的链接,或者直接呈现出来也没有什么意义。但用户还需要这类数据,因此PHP中提供了一种名为Resource(资源)的数据类型。有关这个数据类型的事宜将在第九章中介绍,现在我们只要知道有这么一种数据类型就行了。

zval结构体里的type成员的值便是以上某个IS_*常量之一。内核通过检测变量的这个成员值来知道他是什么类型的数据并做相应的后续处理。

如果要我们检测一个变量的类型,最直接的办法便是去读取它的type成员的值:

void describe_zval(zval *foo)
{
	if (foo->type == IS_NULL)
	{
		php_printf("这个变量的数据类型是: NULL");
    }
    else
    {
        php_printf("这个变量的数据类型不是NULL,这种数据类型对应的数字是: %d", foo->type);
    }
}

上述做法看起来没有错误,但它是一种被强烈禁止一种做法!

PHP内核以后可能会修改变量的实现方式,所以检测type的方法可能在以后就不能用了。为了解决这个兼容问题,zend头文件中定义了大量的宏,供我们检测、操作变量使用,使用这些宏不但让我们的程序更易读,还具有更好的兼容性。这里我们用Z_TYPE_P()宏来改写上面那个程序。

void describe_zval(zval *foo)
{
    if ( Z_TYPE_P(foo) == IS_NULL )
    {
        php_printf("这个变量的数据类型是: NULL");
    }
    else
    {
        php_printf("这个变量的数据类型不是NULL,这种数据类型对应的数字是: %d", foo->type);
    }
}

php_printf()函数是内核对printf()函数的一层封装,我们可以像使用printf()函数那样使用它。

以_P一个p结尾的宏的参数大多是*zval型变量,此外获取变量类型的宏还有两个,分别是Z_TYPE和Z_TYPE_PP,前者的参数是zval型,而后者的参数则是**zval。这样我们便可以猜测一下php内核是如何实现gettype这个函数了,代码如下:

//开始定义php语言中的函数gettype
PHP_FUNCTION(gettype)
{
	//这个arg间接指向就是我们传给gettype函数的参数。是一个zval**结构
	//所以我们要对他使用__PP后缀的宏。
	zval **arg;

	//这个if的操作主要是让arg指向参数~
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z", &arg) == FAILURE) {
		return;
	}
	
	//调用Z_TYPE_PP宏来获取arg指向zval的类型。
	//然后是一个switch结构,RETVAL_STRING宏代表这gettype函数返回的字符串类型的值
	switch (Z_TYPE_PP(arg)) {
		case IS_NULL:
			RETVAL_STRING("NULL", 1);
			break;

		case IS_BOOL:
			RETVAL_STRING("boolean", 1);
			break;

		case IS_LONG:
			RETVAL_STRING("integer", 1);
			break;

		case IS_DOUBLE:
			RETVAL_STRING("double", 1);
			break;
	
		case IS_STRING:
			RETVAL_STRING("string", 1);
			break;
	
		case IS_ARRAY:
			RETVAL_STRING("array", 1);
			break;

		case IS_OBJECT:
			RETVAL_STRING("object", 1);
			break;

		case IS_RESOURCE:
			{
				char *type_name;
				type_name = zend_rsrc_list_get_rsrc_type(Z_LVAL_PP(arg) TSRMLS_CC);
				if (type_name) {
					RETVAL_STRING("resource", 1);
					break;
				}
			}

		default:
			RETVAL_STRING("unknown type", 1);
	}
}

以上三个宏的定义在Zend/zend_operators.h里,定义分别是:

#define Z_TYPE(zval) (zval).type #define Z_TYPE_P(zval_p) Z_TYPE(*zval_p) #define Z_TYPE_PP(zval_pp) Z_TYPE(**zval_pp)

延伸阅读

此文章所在专题列表如下:

  1. PHP内核探索:从SAPI接口开始
  2. PHP内核探索:一次请求的开始与结束
  3. PHP内核探索:一次请求生命周期
  4. PHP内核探索:单进程SAPI生命周期
  5. PHP内核探索:多进程/线程的SAPI生命周期
  6. PHP内核探索:Zend引擎
  7. PHP内核探索:再次探讨SAPI
  8. PHP内核探索:Apache模块介绍
  9. PHP内核探索:通过mod_php5支持PHP
  10. PHP内核探索:Apache运行与钩子函数
  11. PHP内核探索:嵌入式PHP
  12. PHP内核探索:PHP的FastCGI
  13. PHP内核探索:如何执行PHP脚本
  14. PHP内核探索:PHP脚本的执行细节
  15. PHP内核探索:操作码OpCode
  16. PHP内核探索:PHP里的opcode
  17. PHP内核探索:解释器的执行过程
  18. PHP内核探索:变量概述
  19. PHP内核探索:变量存储与类型
  20. PHP内核探索:PHP中的哈希表
  21. PHP内核探索:理解Zend里的哈希表
  22. PHP内核探索:PHP哈希算法设计
  23. PHP内核探索:翻译一篇HashTables文章
  24. PHP内核探索:哈希碰撞攻击是什么?
  25. PHP内核探索:常量的实现
  26. PHP内核探索:变量的存储
  27. PHP内核探索:变量的类型
  28. PHP内核探索:变量的值操作
  29. PHP内核探索:变量的创建
  30. PHP内核探索:预定义变量
  31. PHP内核探索:变量的检索
  32. PHP内核探索:变量的类型转换
  33. PHP内核探索:弱类型变量的实现
  34. PHP内核探索:静态变量的实现
  35. PHP内核探索:变量类型提示
  36. PHP内核探索:变量的生命周期
  37. PHP内核探索:变量赋值与销毁
  38. PHP内核探索:变量作用域
  39. PHP内核探索:诡异的变量名
  40. PHP内核探索:变量的value和type存储
  41. PHP内核探索:全局变量Global
  42. PHP内核探索:变量类型的转换
  43. PHP内核探索:内存管理开篇
  44. PHP内核探索:Zend内存管理器
  45. PHP内核探索:PHP的内存管理
  46. PHP内核探索:内存的申请与销毁
  47. PHP内核探索:引用计数与写时复制
  48. PHP内核探索:PHP5.3的垃圾回收机制
  49. PHP内核探索:内存管理中的cache
  50. PHP内核探索:写时复制COW机制
  51. PHP内核探索:数组与链表
  52. PHP内核探索:使用哈希表API
  53. PHP内核探索:数组操作
  54. PHP内核探索:数组源码分析
  55. PHP内核探索:函数的分类
  56. PHP内核探索:函数的内部结构
  57. PHP内核探索:函数结构转换
  58. PHP内核探索:定义函数的过程
  59. PHP内核探索:函数的参数
  60. PHP内核探索:zend_parse_parameters函数
  61. PHP内核探索:函数返回值
  62. PHP内核探索:形参return value
  63. PHP内核探索:函数调用与执行
  64. PHP内核探索:引用与函数执行
  65. PHP内核探索:匿名函数及闭包
  66. PHP内核探索:面向对象开篇
  67. PHP内核探索:类的结构和实现
  68. PHP内核探索:类的成员变量
  69. PHP内核探索:类的成员方法
  70. PHP内核探索:类的原型zend_class_entry
  71. PHP内核探索:类的定义
  72. PHP内核探索:访问控制
  73. PHP内核探索:继承,多态与抽象类
  74. PHP内核探索:魔术函数与延迟绑定
  75. PHP内核探索:保留类与特殊类
  76. PHP内核探索:对象
  77. PHP内核探索:创建对象实例
  78. PHP内核探索:对象属性读写
  79. PHP内核探索:命名空间
  80. PHP内核探索:定义接口
  81. PHP内核探索:继承与实现接口
  82. PHP内核探索:资源resource类型
  83. PHP内核探索:Zend虚拟机
  84. PHP内核探索:虚拟机的词法解析
  85. PHP内核探索:虚拟机的语法分析
  86. PHP内核探索:中间代码opcode的执行
  87. PHP内核探索:代码的加密与解密
  88. PHP内核探索:zend_execute的具体执行过程
  89. PHP内核探索:变量的引用与计数规则
  90. PHP内核探索:新垃圾回收机制说明

本文地址:http://www.nowamagic.net/librarys/veda/detail/1387,欢迎访问原出处。

不打个分吗?

转载随意,但请带上本文地址:

http://www.nowamagic.net/librarys/veda/detail/1387

如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 加入收藏

阅读一百本计算机著作吧,少年

很多人觉得自己技术进步很慢,学习效率低,我觉得一个重要原因是看的书少了。多少是多呢?起码得看3、4、5、6米吧。给个具体的数量,那就100本书吧。很多人知识结构不好而且不系统,因为在特定领域有一个足够量的知识量+足够良好的知识结构,系统化以后就足以应对大量未曾遇到过的问题。

奉劝自学者:构建特定领域的知识结构体系的路径中再也没有比学习该专业的专业课程更好的了。如果我的知识结构体系足以囊括面试官的大部分甚至吞并他的知识结构体系的话,读到他言语中的一个词我们就已经知道他要表达什么,我们可以让他坐“上位”毕竟他是面试官,但是在知识结构体系以及心理上我们就居高临下。

所以,阅读一百本计算机著作吧,少年!

《php和mysql web开发(原书第4版)》 Luke Welling (作者), Laura Thomson (作者), 武欣 (译者)

《php和mysql web开发(原书第4版)》将PHP开发与MySQL应用相结合,分别对PHP和MySQL做了深入浅出的分析,不仅介绍PHP和MySQL的一般概念,而且对PHP和MySQL的Web应用做了较全面的阐述,并包括几个经典且实用的例子。《php和mysql web开发(原书第4版)》是第4版,经过了全面的更新、重写和扩展,包括PHP 5.3最新改进的特性(例如,更好的错误和异常处理),MySQL的存储过程和存储引擎,Ajax技术与Web 2.0以及Web应用需要注意的安全问题。

更多计算机宝库...