PHP代码实现2 [从变量和数据的角度] 2

常量

常量的数据结构

1
2
3
4
5
6
7
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对于常量的名称在定义时其实是没有所谓的限制

1
2
3
4
5
6
7
8
9
define('^_^', 'smile');

if (defined('^_^')) {
echo 'yes';
}else{
echo 'no';
}
//$var = ^_^; //语法错误
$var = constant("^_^");

通过defined函数测试表示,^_^这个常量已经定义好,这样的常量无法直接调用, 只能使用constant()方法来获取到,否则在语法解析时会报错,因为它不是一个合法的标示符。

常量的等级

除了CONST_CS标记,常量的flags字段通常还可以用CONST_PERSISTENT和CONST_CT_SUBST。

CONST_PERSISTENT表示这个常量需要持久化。这里的持久化内存申请时的持久化是一个概念, 非持久常量会在请求结束时释放该常量,如果读者还不清楚PHP的生命周期,可以参考, PHP生命周期这一小节,也就是说, [如果是非持久常量,会在RSHUTDOWN阶段就将该常量释放,否则只会在MSHUTDOWN阶段将内存释放], 在用户空间,也就是用户定义的常量都是非持久化的,通常扩展和内核定义的常量会设置为持久化, 因为如果常量被释放了,而下次请求又需要使用这个常量,该常量就必须在请求时初始化一次, 而对于常量这些不变的量来说就是个没有意义的重复计算。

通过define()函数定义的常量的模块编号都是PHP_USER_CONSTANT,这表示是用户定义的常量。 除此之外我们在平时使用较多的常量:如错误报告级别E_ALL, E_WARNING等常量就有点不同了。 这些是PHP内置定义的常量,他们属于标准常量。

标准常量注册操作: php_module_startup() -> zend_startup() -> zend_register_standard_constants()]

魔术常量 随着代码的位置而改变

[PHP已经在词法解析时将这些常量换成了对应的值]

几个 PHP 的“魔术常量”
名称 说明
__LINE__ 文件中的当前行号

__FILE__ 文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件名。自 PHP 4.0.2 起,FILE 总是包含一个绝对路径(如果是符号连接,则是解析后的绝对路径),而在此之前的版本有时会包含一个相对路径。

__DIR__ 文件所在的目录。如果用在被包括文件中,则返回被包括的文件所在的目录。它等价于 dirname(FILE)。除非是根目录,否则 目录中名不包括末尾的斜杠。(PHP 5.3.0中新增)

__FUNCTION__ 函数名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该函数被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写> 字母的

__CLASS__ 类的名称(PHP 4.3.0 新加)。自 PHP 5 起本常量返回该类被定义时的名字(区分大小写)。在 PHP 4 中该值总是小写字母的

__METHOD__ 类的方法名(PHP 5.0.0 新加)。返回该方法被定义时的名字(区分大小写)。

__NAMESPACE__ 当前命名空间的名称(大小写敏感)。这个常量是在编译时定义的(PHP 5.3.0 新增)

前面有个比较特殊的地方,当func_name不存在时,FUNCTION被替换成空字符串, 你可能会想,怎么会有变量名不存在的方法呢,这里并不是匿名方法,匿名方法的function_name 并不是空的,而是:”{closure}”, 有兴趣的读者可以去代码找找在那里给定义了。

预定义变量

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

变量赋值

赋值左值存在引用 且左值不等于右值 MMP 这是COW 写时复制啊

1
2
3
4
5
6
7
$a = 10;
$b = &$a;

xdebug_debug_zval('a');

$a = 20;
xdebug_debug_zval('a');

此时Zend engine的实现行动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if (PZVAL_IS_REF(variable_ptr)) { // 如果is_ref_gc != 0
if (variable_ptr!=value) { // 且右值 != 左值
zend_uint refcount = Z_REFCOUNT_P(variable_ptr); 存储refcount

garbage = *variable_ptr; 将老值保存
*variable_ptr = *value; 赋予新的右值
Z_SET_REFCOUNT_P(variable_ptr, refcount); 设置新的refcount
Z_SET_ISREF_P(variable_ptr); 设置新的is ref
if (!is_tmp_var) {
zendi_zval_copy_ctor(*variable_ptr);
}
zendi_zval_dtor(garbage);
return variable_ptr;
}
}

COW介绍: 这是一种推迟内存复制带来的内存管理优化,而当变量的值发生变化时,才会进行重新开辟内存空间,这个机制我们称为写时复制机制

EG:

1
2
3
$i = 4;   //内核创建一个zval指针,并且为其以堆的方式开辟空间,让指针指向这个空间,将zval中的成员引用计数置为1,类型标记为整形,并且申请一个zvalue_value指针,同样以堆的方式以其开辟空间,同时将该联合体中的lval赋值为4,并且在symbal_table的hash表中记录变量i和zval指针的映射关系
$j = $i; //没有在申请内存空间,在zval的成员中引用计数标记为2
$j = 5; //内核重新创建zval指针,重复下上面的步骤,我就不重复说明了,重点是将旧的zval引用计数标记为1

赋值的左值不存在引用,左值的引用计数为1,左值等于右值

1
2
$a = 10;
$a = $a; // 引用计数经历了+1 -1的过程

Zend engine的行为:

1
2
3
4
5
if (Z_DELREF_P(variable_ptr)==0) {  //  引用计数减一操作
if (!is_tmp_var) {
if (variable_ptr==value) {
Z_ADDREF_P(variable_ptr); // 引用计数加一操作
}

赋值的左值不存在引用,左值的引用计数为1,右值存在引用

1
2
3
$a = 10;
$b = &$a;
$c = $a;

这里的$c = $a;的操作就是我们所示的第三种情况。 对于这种情况,ZEND内核直接创建一个新的zval容器,左值的值为右值,并且左值的引用计数为1。 也就是说,这种情形$c不会与$a指向同一个zval。

赋值的左值不存在引用,左值的引用计数为1,右值不存在引用

1
2
$a = 10;
$c = $a;

这时,右值的引用计数加上,一般情况下,会对左值进行垃圾收集操作,将其移入垃圾缓冲池。垃圾缓冲池的功能是在PHP5.3后才有的。

情况五:赋值的左值不存在引用,左值的引用计数为大于0,右值存在引用,并且引用计数大于0

1
2
3
4
5
6
$a = 10;
$b = $a;
$va = 20;
$vb = &$va;

$a = $va;

最后一个操作就是我们的情况五。 使用xdebug看引用计数发现,最终$a变量的引用计数为1,$va变量的引用计数为2,并且$va存在引用。

变量销毁

unset()是一个语法结构, 根据变量不同出发不同的操作

程序会先获取目标符号表,这个符号表是一个HashTable,然后将我们需要unset掉的变量从这个HashTable中删除。 如果对HashTable的元素删除操作成功,程序还会对EX(CVs)内存储的值进行清空操作。 以缓存机制来解释,在删除原始数据后,程序也会删除相对应的缓存内容,以免用户获取到脏数据。

变量作用域

对于全局变量,Zend引擎有一个_zend_executor_globals结构,该结构中的symbol_table就是全局符号表, 其中保存了在顶层作用域中的变量。

同样,函数或者对象的方法在被调用时会创建active_symbol_table来保存局部变量。

函数中的局部变量就存储在_zend_execute_data的symbol_table中,在执行当前函数的op_array时, 全局zend_executor_globals中的active_symbol_table会指向当前_zend_execute_data中的symbol_table。

数据类型转换

  1. 直接的变量赋值操作

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

  3. 强制类型转换

  • 允许进行强制类型转换的类型

(int), (integer) 转换为整型

(bool), (boolean) 转换为布尔类型

(float), (double) 转换为浮点类型

(string) 转换为字符串

(array) 转换为数组

(object) 转换为对象

(unset) 转换为NULL

(unset)\$a(仅仅是类型转换为了null) != unset($a)

猜你喜欢

转载自www.cnblogs.com/xiaoerli520/p/9624228.html