深入理解PHP内核——变量及数据类型

静态类型语言 VS 动态类型语言

从类型的维度来看,编程语言可以分为三大类:

  1. 静态类型语言,比如:C/Java等,在静态语言类型中,类型的检查是在编译期(compile-time)确定的, 也就是说在运行时变量的类型是不会发生变化的。
  2. 动态类型语言,比如:PHP,python等各种脚本语言,这类语言中的类型是在运行时确定的, 那么也就是说类型通常可以在运行时发生变化
  3. 无类型语言,,比如:汇编语言,汇编语言操作的是底层存储,他们对类型毫无感知。

强类型语言 VS 弱类型语言

  • 强类型语言:一旦某个变量被申明为某个类型的变量,在程序运行过程中,就不能将该变量的类型以外的值赋予给它 (当然并不完全如此,这可能会涉及到类型的转换),C/C++/Java等语⾔就属于这类。
  • 弱类型语言:一个变量可以表示任意的数据类型。PHP及Ruby,JavaScript等脚本语言属于弱类型语言。

PHP变量类型及存储结构

深入理解PHP内核——变量及数据类型
官方PHP是用C实现的,而C是强类型的语言,那这是怎么实现PHP中的弱类型的呢?

变量的存储结构

struct __zval_struct {
zvalue_value value;		 //存储变量的值
zend_uint refcount__gc;	//引用计数
zend_uchar type; 			//存储变量的类型
zend_uchar is_ref__gc;	// 表示是否为引用
};

注:PHP在存储变量值时,将变量的类型也保存在同一个结构体中,这样我们就能通过这些信息获取到变量的类型。
注:type的值可以为: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一。

变量值的存储

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中的所有逻辑都围绕变量来进行的,这样的话,内存浪费将是十分大的。这种做法成本小但收益非常大。

各种类型的数据会使用不同的方法来进行变量值的存储,其对应赋值方式如下:

  • boolean 使用lval存储
  • integer 使用lval存储
  • float 使用dbal存储
  • null 不需要存储,设置type标记IS_NULL
  • 字符串String 使用结构体str存储,这里存储了字符串的长度len,使得获取字符串长度的时间复杂度为O(1),空间换取时间。
  • 数组Array 使用ht存储,它是一个HashTable类型的数据。
  • 对象Object 使用obj存储,在面向对象的语言中,我们能够自己定义自己需要的数据类型,包括类的属性、方法等。而对象则是类的一个具体实现,对象有自身的状态和所能完成的操作。PHP的对象是一种复合型的数据,使用一种zend_object_value的结构体来存放。
typedef struct _zend_object_value {
	zend_object_handle handle;			 // unsigned int类型,EG(objects_store).object_buckets的索引
	zend_object_handlers *handlers;
} zend_object_value;

handle:保存object在对象池(EG,object_store)中的索引
handlers:保存对象进行操作时的处理函数

常量

常量的内部结构

typedef struct _zend_constant {
	zval value;					 /* zval结构,PHP内部变量的存储结构 */
	int flags;		 				/* 常量的标记如 CONST_PERSISTENT | CONST_CS */
	char *name;				    /* 常量名称 */
	uint name_len;
	int module_number; 	/* 模块号 */
} zend_constant;

PHP常量的定义过程。一个例子

define('TIPI', 'Thinking In PHP Internal');

常量名称:TIPI,常量值:“Thinking In PHP Internal”
注:CONST_PERSISTENT表示这个常量需要持久化。如果是非持久常量,会在RSHUTDOWN阶段就将该常量释放,否则只会在MSHUTDOWN阶段将内存释放, 在用户空间,也就是用户定义的常量都是非持久化的,通常扩展和内核定义的常量
会设置为持久化, 因为如果常量被释放了,而下次请求又需要使用这个常量,该常量就必须在请求时初始化一次, 而对于常量这些不变的量来说就是个没有意义的重复计算。

魔术常量

PHP提供了大量的预定义常量,有一些是内置的,也有一些是扩展提供的,只有在加载了这些扩展库时才会出现。
不过PHP中有七个魔术常量,他们的值其实是变化的,它们的值随着它们在代码中的位置改变而改变。 所以称他们为魔术常量。例如__LINE__ 的值就依赖于它在脚本中所处的行来决定。 这些特殊的常量不区分大小写。在手册中这几个变量的简单说明如下:
深入理解PHP内核——变量及数据类型

预定义变量

在PHP脚本执行的时候,用户全局变量(在用户空间显式定义的变量)会保存在一个HashTable数据类型的符号表(symbol_table)中, 而我们用得非常多的在全局范围内有效的变量却与这些用户全局变量不同。 例如:GET_GET,_POST,SERVER_SERVER,_FILES等变量,我们并没有在程序中定义这些变量,并且这些变量也同样保存在符号表中, 从这些表象我们不难得出结论:PHP是在脚本运行之前就将这些特殊的变量加入到了符号表。

静态变量

通常意义上静态变量是静态分配的,他们的生命周期和程序的生命周期一样, 只有在程序退出时才结束期生命周期,这和局部变量相反。
深入理解PHP内核——变量及数据类型

全局变量和局部变量

变量按照作用域类型分为:全局变量和局部变量。全局变量是在整个程序中任何地方随意调用的变量,在PHP中,除了声明在函数体内的普通变量均为全局变量,在函数体内则可以通过global语句来声明。相对于全局变量,局部变量的作用域是程序中的部分代码(如函数中),而不是程序的全部。
变量的作用域与变量的生命周期有一定的联系,如在一个函数中定义的变量,这个变量的作用域从变量声明的时候开始到这个函数结束的时候。这种变量,我们称之为局部变量。它的生命周期开始于函数开始,结束于函数的调用完成之时。
变量的作用域决定其生命周期吗?程序运行到变量作用域范围之外,就会将变量进行销毁吗?
深入理解PHP内核——变量及数据类型

Global关键字

global语句的作用是定义全局变量,例如如果想在函数内访问全局作用域内的变量则可以通过global声明来定义。

数据类型转换

隐式转换

直接的变量赋值操作

运算结果对变量的赋值操作

1、表达式的操作数为同一数据类型 这种情况的作用以上面的直接变量的类型转换是同一种情况,只是此时右值变成了表达式的运算结果。
2、表达式的操作数不为同的数据类型 这种情况的类型转换发生在表达式的运算符的计算过程中,在源码中也就是发生在运算符的实现过程中。

显式类型转换(强制类型转换)

深入理解PHP内核——变量及数据类型
内容源自:《TIPI_深入理解PHP内核》