深入理解PHP内核(九)变量及数据类型-静态变量
原文链接:http://www.orlion.ga/251/
通常静态变量是静态分配的,他们的生命周期和程序的生命周期一样长,只有在程序退出后才结束生命周期,这和局部变量相反,有的语言中全局变量也是静态分配的,例如PHP和js中的全局变量。
静态变量可以分为:
静态全局变量,PHP中的全局变脸也可以理解为静态全局变量,因为除了明确unset释放,在程序运行过程中始终存在。
静态局部变量,也就是在函数内部定义的静态变量,函数在执行时对变量的操作会保持到下一次函数被调用
静态成员变量,这是在类中定义的静态变量,和实例变量相对应,静态成员变量可以在所有实例中共享。
最常见的是静态局部变量和静态成员变量。局部变量只有在函数执行时才会存在。通常,当一个函数执行完毕,它的局部变量的值就已经不存在了,而且变量所占据的内存也被释放,当下一次执行该过程的时候,所有的局部变量将重新被初始化,如果某个静态变量定义为静态的,那它的值就不会在函数调用结束后释放,而是继续保留变量的值。
本文将介绍静态局部变量
先看一下php中局部变量的使用:
function t() {
static $i = 0;
$i++;
echo $i, ' ';
}
t();
t();
t();
上面的程序会输出1 2 3。从这个例子可以看出$i变量的值在改变后函数继续执行还能访问到,$i变量就像是只有函数t()才能访问到的一个全局变量。那PHP是怎么实现的呢?
static是PHP的关键字,我们从词法分析,语法分析,中间代码生成到执行中间代码这几个部分探讨整个过程。
1、词法分析
首先查看Zend/zend_language_scanner.l文件,搜索static关键字,我们可以找到如下代码:
<ST_IN_SCRIPTING>"static" {
return T_STATIC;
}
2、语法分析
在词法分析找到token后,通过这个token,在Zend/zend_language_parser.y文件中查找,找到代码如下:
| T_STATIC static_var_list ';'
static_var_list:
static_var_list ',' T_VARIABLE { zend_do_fetch_static_variable(&$3,
NULL, ZEND_FETCH_STATIC TSRMLS_CC); }
| static_var_list ',' T_VARIABLE '=' static_scalar {
zend_do_fetch_static_variable(&$3, &$5, ZEND_FETCH_STATIC TSRMLS_CC); }
| T_VARIABLE { zend_do_fetch_static_variable(&$1, NULL,
ZEND_FETCH_STATIC TSRMLS_CC); }
| T_VARIABLE '=' static_scalar { zend_do_fetch_static_variable(&$1, &$3,
ZEND_FETCH_STATIC TSRMLS_CC); }
;
语法分析的过程中如果匹配到相应的模式则会进行相应的处理动作,通常是进行opcode的编译,在本例中的static关键字匹配中,是由函数zend_do_fetch_static_variable处理的。
3、生成opcode中间代码
zend_do_fetch_static_variable函数的作用就是生成opcode,定义如下:
void zend_do_fetch_static_variable(znode *varname, const znode
*static_assignment, int fetch_type TSRMLS_DC)
{
zval *tmp;
zend_op *opline;
znode lval;
znode result;
ALLOC_ZVAL(tmp);
if (static_assignment) {
*tmp = static_assignment->u.constant;
} else {
INIT_ZVAL(*tmp);
}
if (!CG(active_op_array)->static_variables) { /* ɩ ȐǦÁ
ʦɖĻļŴůǎ
ę */
ALLOC_HASHTABLE(CG(active_op_array)->static_variables);
zend_hash_init(CG(active_op_array)->static_variables, 2, NULL,
ZVAL_PTR_DTOR, 0);
}
// 8
ʦɖĻļůė7
zend_hash_update(CG(active_op_array)->static_variables, varname-
>u.constant.value.str.val,
varname->u.constant.value.str.len+1, &tmp, sizeof(zval *), NULL);
...//˯ɐ
opline = get_next_op(CG(active_op_array) TSRMLS_CC);
opline->opcode = (fetch_type == ZEND_FETCH_LEXICAL) ? ZEND_FETCH_R :
ZEND_FETCH_W; /* Îwfetch_type=ZEND_FETCH_STATICJĨNǬZEND_FETCH_W*/
opline->result.op_type = IS_VAR;
opline->result.u.EA.type = 0;
opline->result.u.var = get_temporary_variable(CG(active_op_array));
opline->op1 = *varname;
SET_UNUSED(opline->op2);
opline->op2.u.EA.type = ZEND_FETCH_STATIC; /* fKƣ%ĉÁNUus */
result = opline->result;
if (varname->op_type == IS_CONST) {
zval_copy_ctor(&varname->u.constant);
}
fetch_simple_variable(&lval, varname, 0 TSRMLS_CC); /* Relies on the fact
that the default fetch is BP_VAR_W */
if (fetch_type == ZEND_FETCH_LEXICAL) {
...//˯ɐ
} else {
zend_do_assign_ref(NULL, &lval, &result TSRMLS_CC); // ɶŔKƣ%
oÒ
}
CG(active_op_array)->opcodes[CG(active_op_array)->last-1].result.u.EA.type
|= EXT_TYPE_UNUSED;
}
从上面的代码我们可知,在解释成中间代码时,静态变量是存放在CG(active_op_array)->static_variable中的。并且生成中间代码为ZEND_FETCH_W和ZEND_ASSIGN_REF。其中ZEND_FETCH_W中间代码是在zend_do_fetch_static_variable中直接赋值,而ZEND_ASSIGN_REF中间代码是在zend_do_fetch_static_variable中调用zend_do_assign_ref生成的。
4、执行中间代码
opcode的编译阶段完成后就开始opcode的执行了。在Zend/zend_vm_opcodes.h文件中包含所有opcode的宏定义,这些宏没有特殊含义,只是作为opcode的唯一标示,包含本例中相关的如下两个宏的定义:
#define ZEND_FETCH_W 83
#define ZEND_ASSIGN_REF 39
深入理解PHP内核(三)概览-PHP脚本的执行一文中介绍了根据opcode查找到相应处理函数的方法,通过中间代码调用映射方法计算得此时ZEND_FETCH_W对应的操作为ZEND_FETCH_W_SPEC_CV_HANDLER。其代码如下:
static int ZEND_FASTCALL
ZEND_FETCH_W_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
return zend_fetch_var_address_helper_SPEC_CV(BP_VAR_W,
ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);
}
static int ZEND_FASTCALL zend_fetch_var_address_helper_SPEC_CV(int type,
ZEND_OPCODE_HANDLER_ARGS)
{
...// 省略
if (opline->op2.u.EA.type == ZEND_FETCH_STATIC_MEMBER) {
retval = zend_std_get_static_property(EX_T(opline-
>op2.u.var).class_entry, Z_STRVAL_P(varname), Z_STRLEN_P(varname), 0
TSRMLS_CC);
} else {
// 取符号表,这里我们取得是EG(active_op_array)->static_variables
target_symbol_table = zend_get_target_symbol_table(opline, EX(Ts),
type, varname TSRMLS_CC);
...// 省略
if (zend_hash_find(target_symbol_table, varname->value.str.val,
varname->value.str.len+1, (void **) &retval) == FAILURE) {
switch (type) {
...// 省略
// 在前面的调用中我们知道type = case BP_VAR_W,于是程序会走按case BP_VAR_W的流程走
case BP_VAR_W: {
zval *new_zval = &EG(uninitialized_zval);
Z_ADDREF_P(new_zval);
zend_hash_update(target_symbol_table, varname-
>value.str.val, varname->value.str.len+1, &new_zval, sizeof(zval *), (void **)
&retval);
// 更新符号表,执行赋值操作
}
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
}
switch (opline->op2.u.EA.type) {
...// 省略
case ZEND_FETCH_STATIC:
zval_update_constant(retval, (void*) 1 TSRMLS_CC);
break;
case ZEND_FETCH_GLOBAL_LOCK:
if (IS_CV == IS_VAR && !free_op1.var) {
PZVAL_LOCK(*EX_T(opline->op1.u.var).var.ptr_ptr);
}
break;
}
}
...// 省略
}
在上面的代码中有一个关键的函数zend_get_target_symbol_table。它的作用是获取当前正在执行的目标符号表,而在函数执行时当前的op_array则是函数体本身,先看看zend_op_array的结构。
struct _zend_op_array {
/* Common elements */
zend_uchar type;
char *function_name;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
zend_bool pass_rest_by_reference;
unsigned char return_reference;
/* END of common elements */
zend_bool done_pass_two;
zend_uint *refcount;
zend_op *opcodes;
zend_uint last, size;
/* static variables support */
HashTable *static_variables;
zend_op *start_op;
int backpatch_count;
zend_uint this_var;
// ...
}
由上边可以看到zend_op_array中包含function_name字段,也就是当前函数的名称。再看看获取当前符号表的函数:
static inline HashTable *zend_get_target_symbol_table(const zend_op *opline,
const temp_variable *Ts, int type, const zval *variable TSRMLS_DC)
{
switch (opline->op2.u.EA.type) {
...// ˯ɐ
case ZEND_FETCH_STATIC:
if (!EG(active_op_array)->static_variables) {
ALLOC_HASHTABLE(EG(active_op_array)->static_variables);
zend_hash_init(EG(active_op_array)->static_variables, 2, NULL,
ZVAL_PTR_DTOR, 0);
}
return EG(active_op_array)->static_variables;
break;
EMPTY_SWITCH_DEFAULT_CASE()
}
return NULL;
}
在当前的zend_do_fetch_static_variable执行时,op2.u.EA.type的值为ZEND_FETCH_STATIC,从而这zend_get_target_symbol_table函数我们返回的是EG(active_op_array)->static_variable。也就是当前函数的静态变量哈希表。每次执行时都会从该符号表中查找相应的值,由于op_array在程序执行时始终存在。所有对静态符号表中数值的修改会继续保留,下次函数执行时继续从符号表获取信息。也就是说Zend为每个函数(准确的说是zend_op_array)分配了一个私有的符号表来保存该函数的静态变量。
深入理解PHP内核(九)变量及数据类型-静态变量的更多相关文章
- C++中的局部变量、全局变量、局部静态变量、全局静态变量的区别
局部变量(Local variables)与 全局变量: 在子程序或代码块中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量. 全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序 ...
- 论TEMP临时变量与VAR静态变量
TEMP临时变量:顾名思义,这种变量类型是临时的,没有固定的存放数据的内存空间.每次扫描结束后则清零,在下个扫描周期开始时,这个变量的值都是不确定的,一般为0.使用临时变量需要遵循一个原则:先赋值再使 ...
- 论TEMP临时变量与VAR静态变量区别
TEMP临时变量:顾名思义,这种变量类型是临时的,没有固定的存放数据的内存空间.每次扫描结束后则清零,在下个扫描周期开始时,这个变量的值都是不确定的,一般为0.使用临时变量需要遵循一个原则:先赋值再使 ...
- 深入理解PHP内核(十四)类的成员变量及方法
原文链接:http://www.orlion.ga/1237/ 类的成员变量在PHP中本质是一个变量,只是这些变量都归属于某个类,并且给这些变量是有访问控制的. 类的成员方法在PHP中本质是一个函数, ...
- iOS开发环境C语言基础 变量 、 数据类型和变量赋值 、 其他 、 运算符
1 变量使用常用错误汇总 1.1 问题 在我们使用变量的过程中,会遇到一些问题,在此将这些问题进行汇总,在今后使用的过程中,不出错.即使出现错误也可以很快地找到问题所在. 1.2 方案 变量在使用的过 ...
- c#静态变量和非静态变量的区别
静态变量的类型说明符是static.静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量,例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成 ...
- Java代码执行顺序(静态变量,非静态变量,静态代码块,代码块,构造函数)加载顺序
//据说这是一道阿里巴巴面试题,先以这道题为例分析下 public class Text { public static int k = 0; public static Text t1 = new ...
- Java的外部类和内部类+静态变量和非静态变量的组合关系
看的李刚<疯狂java讲义>,里面讲内部类的地方感觉有点散而且不全,看完之后还是不十分清楚到底怎么用,于是自己写了个程序测试了一下.看如下代码,即可知道外部类和内部类+静态成员和非静态成员 ...
- Java变量类型,实例变量 与局部变量 静态变量
实例变量: 实例变量在类中声明,但在方法的外面,构造函数或任何块. 当空间分配给某个对象在堆中,插槽为每个实例变量创建值. 当一个对象与使用关键字 “new” 来创建,在对象被销毁销毁创建的实例变量. ...
随机推荐
- [Leetcode][JAVA] Recover Binary Search Tree (Morris Inorder Traversal)
Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without changing ...
- PYTHON学习之路_PYTHON基础(1)
学习内容: 1.Python介绍 2.Python程序初接触和变量 3.Python用户交互 4.Python数据类型 5.Python循环if...(elif)...else 6.Python循环w ...
- 攒机I7
CPU : I7 4790K +Z97 = 3200 散热器 :九州风神玄冰400 = 99 硬盘 :希捷 1TB 64M = 310 机箱: 金河田超越白 = 200 内存 DDR3金士顿8G = ...
- php图片处理类库 Image
image 下载地址 https://github.com/Intervention/image.git 下载之后解压 执行composer update 生成 autoload.php文件 该类 ...
- java分割字符串
经验分享: 1.分隔符为“.”(无输出),“|”(不能得到正确结果)转义字符时,“*”,“+”时出错抛出异常,都必须在前面加必须得加"\\",如split(\\|); 2.如果用& ...
- ng2-timesheet, 一个timesheet.js的angular2复制版
一个 timesheet.js (JavaScript library for HTML5 & CSS3 time sheets) 的 Angular 2 复制版 用法: npm instal ...
- ORM查询语言(OQL)简介--高级篇(续):庐山真貌
相关文章内容索引: ORM查询语言(OQL)简介--概念篇 ORM查询语言(OQL)简介--实例篇 ORM查询语言(OQL)简介--高级篇:脱胎换骨 ORM查询语言(OQL)简介--高级篇(续):庐山 ...
- 【Bugly技术干货】那些年我们用过的显示性能指标
Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言: 注:Google 在自己文 ...
- 设计模式之美:Visitor(访问者)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Visitor 模式结构样式代码. 实现方式(二):使用 Visitor 模式解构设计. 实现方式(三):使用 Acyclic ...
- angular ng-model类型格式转化
在angular开发中我们经常会遇见输入框中的string的值,却想在scope上的model表现为整型.浮点.货币,或者在radio的value是一个true,false的Boolean类型,一组c ...