常量

常量的数据结构

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. Vue 之 element-ui upload组件的文件类型

    在使用element-ui的upload上传组件的时候,有时候会遇到 控制上传文件类型 的需求,只需要配置accept属性为允许的类型即可,比如: <el-upload class=" ...

  2. ecmall 支付成功 订单状态没有改变解决办法

    问题原因:ecmall版本较低,或者是没有更新支付宝接口导致的.支付宝修改了返回参数,给支付宝增加了一个返回的状态:TRADE_SUCCESS. 解决方案: 需要修改“includes\payment ...

  3. google和baidu搜索命令

    在google里面搜索一点儿老外的资料的时候发现搜不到我想要的东西,以前貌似见过一个搜索命令的但是一时想不起来了,所以就去搜索了一下搜索命令,常用的在这里全部列举出来: google:绿色的较为常用的 ...

  4. day01计算机组成与操作系统

    1.什么是编程语言编程语言是程序员与计算机之间的沟通介质 2.什么是编程编程的过程就是程序员通过某种语言将命令给到计算机并让计算机表达出来 修改后:编程就是程序员按照某种语法规则将自己想让计算机做的事 ...

  5. 你不能阻止DOM

    浏览器数据库景观 对于外行来说,浏览器数据库的世界可能是一个令人困惑的世界.Lawnchair,PouchDB,LocalForage,Dexie,Lovefield,LokiJS,AlaSQL,Ma ...

  6. django ---forms组件

    forms组件 本文目录 1 校验字段功能 2 渲染标签功能 3 渲染错误信息功能 4 组件的参数配置 5 局部钩子 6 全局钩子 回到目录 1 校验字段功能 针对一个实例:注册用户讲解. 模型:mo ...

  7. Win-Lin双系统重装Windows找回Linux启动

    第一系统Windows,第二系统Linux:Ubuntu18.10: 1. 重新安装Windows系统后,使用Ubuntu的安装光盘,或启动U盘启动电脑:2. 选择:Try Ubuntu ;3. 进入 ...

  8. 【BZOJ3894】文理分科

    最小割劲啊 原题:  文理分科是一件很纠结的事情!(虽然看到这个题目的人肯定都没有纠 结过)  小P所在的班级要进行文理分科.他的班级可以用一个n*m的矩阵进行 描述,每个格子代表一个同学的座位.每位 ...

  9. MySQL Binlog--binlog_format参数

    ===================================================================================== binlog_format参 ...

  10. Intellij IDEA2017.3永久激活方法

    随着idea不断地发展,eclipse的缺点日渐明显,为了能够获得良好的编码体验,越来越多的朋友转向了idea,淘汰了eclipse但是由于近期idea所有私人服务器被封杀了,很多喜欢编码的朋友们都陷 ...