常量

常量的数据结构

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)

PHP代码实现2 [从变量和数据的角度] 2的更多相关文章

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

    PHP代码实现2 [从变量和数据的角度] 1 数据类型 1.静态类型语言,比如:C/Java等,在静态语言类型中,类型的检查是在<编译>(compile-time)确定的, 也就是说在运行 ...

  2. 019 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 13 数据类型转换的代码示例

    019 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 13 数据类型转换的代码示例 本文知识点:Java中的数据类型转换案例 学习视频有误,导致没法写文,文章内容 ...

  3. java中静态的代码块,静态变量,静态方法

    简单了解一下java虚拟机--jvm几个内存区域: 方法区:在java的虚拟机中有一块专门用来存放已经加载的类信息.常量.静态变量以及方法代码的内存区域, 常量池:常量池是方法区的一部分,主要用来存放 ...

  4. R语言通过loess去除某个变量对数据的影响

      当我们想研究不同sample的某个变量A之间的差异时,往往会因为其它一些变量B对该变量的固有影响,而影响不同sample变量A的比较,这个时候需要对sample变量A进行标准化之后才能进行比较.标 ...

  5. R语言通过loess去除某个变量对数据的影响--CNV分析

    当我们想研究不同sample的某个变量A之间的差异时,往往会因为其它一些变量B对该变量的固有影响,而影响不同sample变量A的比较,这个时候需要对sample变量A进行标准化之后才能进行比较.标准化 ...

  6. 线程系列07,使用lock语句块或Interlocked类型方法保证自增变量的数据同步

    假设多个线程共享一个静态变量,如果让每个线程都执行相同的方法每次让静态变量自增1,这样的做法线程安全吗?能保证自增变量数据同步吗?本篇体验使用lock语句块和Interlocked类型方法保证自增变量 ...

  7. 在activity之间通过静态变量传递数据

    在activity之间通过静态变量传递数据 一.简介 主要作用:解决intent不能传递非序列化的对象 评价:简单方便,但是容易发生内存泄露,所以要及时回收内存 二.具体操作 1.在传输数据的页面弄好 ...

  8. java静态类、静态方法、静态代码块,静态变量及实例方法,实例变量初始化顺序及内存管理,机制

    1.当一个类被第一次使用时,它需要被类加载器加载,而加载过程涉及以下两点: (1)在加载一个类时,如果它的父类还未被加载,那么其父类必须先被加载: (2)当类加载到内存之后,按照在代码中的出现顺序执行 ...

  9. 【黑马JavaSE】1.1JavaSE、环境变量、CMD使用、常量、变量、数据类型转换(自动/强制)、ASCII码表、Unicode万国码表

    文章目录 SUN公司,詹姆斯.劳瑟琳,Java祖师爷 Java语言开发环境搭建 把Java添加到环境变量的方法 命令行CMD里一些报的错误 命令控制行常用操作的代码展示 Notepad++.注释.标识 ...

随机推荐

  1. 1.带宽&吞吐量

    1.带宽         网络带宽是指在一个固定的时间内(1秒),能通过的最大位数据.就好象高速公路的车道一样,带宽越大,好比车道越多 带宽是一个非常有用的概念,在网络通信中的地位十分重要.带宽的实际 ...

  2. Springmvc+WebSocket整合

    WebSocket是为解决客户端与服务端实时通信而产生的技术.其本质是先通过HTTP/HTTPS协议进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信. 以前我 ...

  3. sharpkeys键盘按键重映射

    /********************************************************************** * sharpkeys键盘按键重映射 * 说明: * 键 ...

  4. EF数据库配置

    <?xml version="1.0" encoding="utf-8"?> <configuration> <configSec ...

  5. animate.css动画

    添加类名的时间不要只添加动画的类名,也要加上animated,使用的时间可以把自己需要的效果复制出来

  6. Java 源码解析

    Object equals方法对比两个对象是否是内存中同一个物理地址 hashCode规定,当两个对象相等时,必须返回相等的hashCode,所以重写equals方法有必要重写hashCode方法 如 ...

  7. ajax解决跨域

    http://www.cnblogs.com/sunxucool/p/3433992.html 为什么会出现跨域跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相 ...

  8. Mybatis(七)-- LRU LFU 算法

    这篇博客主要介绍LRU LFU 算法,因为在Mybatis的缓存中会用到,所以放到这个系列中了.此外,这是我翻译的一篇文章,觉得原文已经写的很好了,所以就直接翻译一下,留作知识整理. 英文原文出处如下 ...

  9. ansible-playbook入门实例解析

    [root@localhost tlsit]# ansible-playbook a.yml PLAY [test] ***************************************** ...

  10. day1 python学习

    Python的分类 编译型:一次性,将全部程序编译成二进制文件,然后再运行. 有点:执行效率高 缺点:开发效率低,不能跨平台使用. 解释型:当你程序运行时,一行一行的解释,并运行 优点:开发效率高,可 ...