php的op_array与execute_data的关系

先打印一下php调用过程:

php的op_array与execute_data的关系_第1张图片


php的op_array与execute_data的关系_第2张图片

在增加一张异常调用的流程图:


今天稍微对php做下总结,首先介绍最重要的两个数据结构,以及两个结构间的数据传递

struct _zend_op_array {
/* Common elements */
zend_uchar type;
const char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
union _zend_function *prototype;
zend_uint num_args;
zend_uint required_num_args;
zend_arg_info *arg_info;
/* END of common elements */


zend_uint *refcount;


zend_op *opcodes;
zend_uint last;


zend_compiled_variable *vars;
int last_var;


zend_uint T;

zend_literal *literals;
int last_literal;


...
};



不重点介绍的属性暂时省略


struct _zend_execute_data {
struct _zend_op *opline;
zend_function_state function_state;
zend_op_array *op_array;
zval *object;
HashTable *symbol_table;
struct _zend_execute_data *prev_execute_data;
zval *old_error_reporting;
zend_bool nested;
zval **original_return_value;
zend_class_entry *current_scope;
zend_class_entry *current_called_scope;
zval *current_this;
struct _zend_op *fast_ret; /* used by FAST_CALL/FAST_RET (finally keyword) */
zval *delayed_exception;
call_slot *call_slots;
call_slot *call;
};


zend_execute_data的数据结构大部分是指针,指针指向的内容是这样分配的
5.6很清楚的画出了内存分配图
/*
 * Stack Frame Layout (the whole stack frame is allocated at once)
 * ==================
 *
 *                             +========================================+
 *                             | zend_execute_data                      |<---+
 *                             |     EX(function_state).arguments       |--+ |
 *                             |  ...                                   |  | |
 *                             | ARGUMENT [1]                           |  | |
 *                             | ...                                    |  | |
 *                             | ARGUMENT [ARGS_NUMBER]                 |  | |
 *                             | ARGS_NUMBER                            |<-+ |
 *                             +========================================+    |
 *                                                                           |
 *                             +========================================+    |
 *                             | TMP_VAR[op_arrat->T-1]                 |    |
 *                             | ...                                    |    |
 *     EX_TMP_VAR_NUM(0) ----> | TMP_VAR[0]                             |    |
 *                             +----------------------------------------+    |
 * EG(current_execute_data) -> | zend_execute_data                      |    |
 *                             |     EX(prev_execute_data)              |----+
 *                             +----------------------------------------+
 *     EX_CV_NUM(0) ---------> | CV[0]                                  |--+
 *                             | ...                                    |  |
 *                             | CV[op_array->last_var-1]               |  |
 *                             +----------------------------------------+  |
 *                             | Optional slot for CV[0] zval*          |<-+
 *                             | ...                                    |
 *                             | ...for CV [op_array->last_var-1] zval* |
 *                             +----------------------------------------+
 *           EX(call_slots) -> | CALL_SLOT[0]                           |
 *                             | ...                                    |
 *                             | CALL_SLOT[op_array->nested_calls-1]    |
 *                             +----------------------------------------+
 * zend_vm_stack_frame_base -> | ARGUMENTS STACK [0]                    |
 *                             | ...                                    |
 * zend_vm_stack_top --------> | ...                                    |
 *                             | ...                                    |
 *                             | ARGUMENTS STACK [op_array->used_stack] |
 *                             +----------------------------------------+
 */
这里分配有个条件判断在5.2是没有的
(op_array->fn_flags & ZEND_ACC_GENERATOR) != 0
当如果用到了yield函数时就会触发该逻辑,从而再分配上面的堆栈结构是会给prev_execute_data单独分配空间,并且指向TMP_VAR变量的上面的内存位置。
这个数据结构重要的是三个属性EX_TMP_VAR_NUM(临时变量的空间),EX_CV_NUM(缓存变量的空间),zend_vm_stack_top(函数参数的空间)



注释下php7的结构简化了不少

/*
 * Stack Frame Layout (the whole stack frame is allocated at once)
 * ==================
 *
 *                             +========================================+
 * EG(current_execute_data) -> | zend_execute_data                      |
 *                             +----------------------------------------+
 *     EX_CV_NUM(0) ---------> | VAR[0] = ARG[1]                        |
 *                             | ...                                    |
 *                             | VAR[op_array->num_args-1] = ARG[N]     |
 *                             | ...                                    |
 *                             | VAR[op_array->last_var-1]              |
 *                             | VAR[op_array->last_var] = TMP[0]       |
 *                             | ...                                    |
 *                             | VAR[op_array->last_var+op_array->T-1]  |
 *                             | ARG[N+1] (extra_args)                  |
 *                             | ...                                    |
 *                             +----------------------------------------+
 */



稍微了解了上面两个数据结构后,下面就讲zend_op_array与zend_execute之间的相互关系
php分为几个阶段包括生成opcode阶段和执行opcode阶段,其实分别对应的就是上面两个数据结构,
并且两个数据结构都是在解析到新的函数时分配新的空间,然后层层嵌套,最外层总是有个大的op_array与execute_data,具体点说就是这两个数据结构存储的是当前函数下的变量环境。
然后就是上面两个不同阶段存储该阶段应该存储的数据,然后可供下一层调用。


第一个例子$var = 1

此处省略掉语法解析(| variable '=' expr{ zend_check_writable_variable(&$1); zend_do_assign(&$$, &$1, &$3 TSRMLS_CC); }),

直接到opconde生成阶段


以下代码是var该字符串代表的变量的信息,为什么这么说因为现在它还不能成为变量,opconde解析的$var的返回值才是变量
fetch_simple_variable -> lookup_cv
核心代码
i = op_array->last_var;

op_array->last_var++;

void fetch_simple_variable_ex(znode *result, znode *varname, int bp, zend_uchar op TSRMLS_DC) /* {{{ */
{
	zend_op opline;
	zend_op *opline_ptr;
	zend_llist *fetch_list_ptr;

	if (varname->op_type == IS_CONST) {
		ulong hash;

		if (Z_TYPE(varname->u.constant) != IS_STRING) {
			convert_to_string(&varname->u.constant);
		}

		hash = str_hash(Z_STRVAL(varname->u.constant), Z_STRLEN(varname->u.constant));
		if (!zend_is_auto_global_quick(Z_STRVAL(varname->u.constant), Z_STRLEN(varname->u.constant), hash TSRMLS_CC) &&
		    !(Z_STRLEN(varname->u.constant) == (sizeof("this")-1) &&
		      !memcmp(Z_STRVAL(varname->u.constant), "this", sizeof("this") - 1)) &&
		    (CG(active_op_array)->last == 0 ||
		     CG(active_op_array)->opcodes[CG(active_op_array)->last-1].opcode != ZEND_BEGIN_SILENCE)) {
			result->op_type = IS_CV;
			result->u.op.var = lookup_cv(CG(active_op_array), Z_STRVAL(varname->u.constant), Z_STRLEN(varname->u.constant), hash TSRMLS_CC);
			Z_STRVAL(varname->u.constant) = (char*)CG(active_op_array)->vars[result->u.op.var].name;
			result->EA = 0;
			return;
		}
	}

	if (bp) {
		opline_ptr = &opline;
		init_op(opline_ptr TSRMLS_CC);
	} else {
		opline_ptr = get_next_op(CG(active_op_array) TSRMLS_CC);
	}

	opline_ptr->opcode = op;
	opline_ptr->result_type = IS_VAR;
	opline_ptr->result.var = get_temporary_variable(CG(active_op_array));
	SET_NODE(opline_ptr->op1, varname);
	GET_NODE(result, opline_ptr->result);
	SET_UNUSED(opline_ptr->op2);
	opline_ptr->extended_value = ZEND_FETCH_LOCAL;

	if (varname->op_type == IS_CONST) {
		CALCULATE_LITERAL_HASH(opline_ptr->op1.constant);
		if (zend_is_auto_global_quick(Z_STRVAL(varname->u.constant), Z_STRLEN(varname->u.constant), Z_HASH_P(&CONSTANT(opline_ptr->op1.constant)) TSRMLS_CC)) {
			opline_ptr->extended_value = ZEND_FETCH_GLOBAL;
		}
	}

	if (bp) {
		zend_stack_top(&CG(bp_stack), (void **) &fetch_list_ptr);
		zend_llist_add_element(fetch_list_ptr, opline_ptr);
	}
}

如果是$$var就会走到下面逻辑进行opline赋值,当execute阶段就会执行 ZEND_FETCH_W_SPEC_CV_VAR_HANDLER找到$var变量的值再进行变量查询.



op_array->vars[i].name = zend_new_interned_string(name, name_len + 1, 1 TSRMLS_CC);
op_array->vars[i].name_len = name_len;
op_array->vars[i].hash_value = hash_value;
很好理解,为$var这个变量属性分配了空间而不是为变量分配空间,利用了op_array两个属性last_var变量位置和vars数组的对应关系


那$var返回值是什么呢
result->op_type = IS_CV;
result->u.op.var = op_array->last_var;

但是前提大家需要知道语法解析时将字符串或者整形统一解析到znode->u.constant中,

result的数据结构是zonde,

后续opcode阶段如果是变量赋值的是znode_op的var属性,即偏移量,znode_op.zv属性,即常量信息(该值有pass_two赋值)

typedef union _znode_op {
	zend_uint      constant;
	zend_uint      var;
	zend_uint      num;
	zend_ulong     hash;
	zend_uint      opline_num; /*  Needs to be signed */
	zend_op       *jmp_addr;
	zval          *zv;
	zend_literal  *literal;
	void          *ptr;        /* Used for passing pointers from the compile to execution phase, currently used for traits */
} znode_op;

typedef struct _znode { /* used only during compilation */
	int op_type;
	union {
		znode_op op;
		zval constant; /* replaced by literal/zv */
		zend_op_array *op_array;
		zend_ast *ast;
	} u;
	zend_uint EA;      /* extended attributes */
} znode;

这里补充下,语法扫描获取znode后,进入语法解析阶段,此刻op_array中有个特殊属性literals,该属性是个数组会提前分配好,最终可进行opcode阶段优化见update_op1_const函数,将变量转为常量。

 
  
accel_startup:
accelerator_orig_compile_file = zend_compile_file; // 保存原生handle
zend_compile_file = persistent_compile_file; //赋值新的handle
用persistent_compile_file -> compile_and_cache_file -> cache_script_in_shared_memory -> zend_accel_script_optimize
-> zend_accel_optimize-> zend_optimize-> replace_var_by_const->update_op1_const最后会利用literals数组将变量转换为常量更改opline的op1或者op2


该数组index与value方式进行存储

#define SET_NODE(target, src) do { \
		target ## _type = (src)->op_type; \
		if ((src)->op_type == IS_CONST) { \
			target.constant = zend_add_literal(CG(active_op_array), &(src)->u.constant TSRMLS_CC); \
		} else { \
			target = (src)->u.op; \
		} \
	} while (0)

如果是常量op1->constant = index,  其种value存在将数据存到literals中,接下来的用途见pass_two;

compile->pass_two 此时会生成opcode的回调op->handler,并且会从constant的index中将value赋值给opline中的op1.zv,

这样在真正execute阶段用的就是op1.zv获取常量信息

while (opline < end) {
		if (opline->op1_type == IS_CONST) {
			opline->op1.zv = &op_array->literals[opline->op1.constant].constant;
		}
		if (opline->op2_type == IS_CONST) {
			opline->op2.zv = &op_array->literals[opline->op2.constant].constant;
		}
		。。。。。。
		
		ZEND_VM_SET_OPCODE_HANDLER(opline);
		opline++;
	}

#define IS_CONST	(1<<0)
#define IS_TMP_VAR	(1<<1)
#define IS_VAR		(1<<2)
#define IS_UNUSED	(1<<3)	/* Unused variable */
#define IS_CV		(1<<4)	/* Compiled variable */
如:
opline->result_type = IS_TMP_VAR; //
opline->result.var = get_temporary_variable(CG(active_op_array));

opline->result_type = IS_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
最后生成的函数就是
ZEND_ASSIGN_SPEC_VAR_TMP_HANDLER
VAR与TMP均来自临时变量,但是两者用的数据结构不同,具体可见以下两个函数
 
  
value = _get_zval_ptr_tmp(opline->op2.var, execute_data, &free_op2 TSRMLS_CC);
variable_ptr_ptr = _get_zval_ptr_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);

完!


//又利用了op_array的一个属性op_array>T++,//此刻仍然是个位置变量,要知道一个op_array可以有n多变量,这个属性就像一个全局数组,所以位置var//足可以代表一个变量。


上面分析的是($)(var)的过程
接下来该赋值了通过语法解析知道调用的是zend_do_assign函数
原型zend_do_assign(znode *result, znode *variable, znode *value TSRMLS_DC)
result是返回值,variable就是上面介绍的变量的返回值(result.op_type),value就是常量1
opline->handle = ZEND_ASSIGN_SPEC_CV_CONST_HANDLER


刚才说过opcode的解析阶段完成,接下来就是要执行opcode了
ZEND_ASSIGN_SPEC_CV_CONST_HANDLER -> _get_zval_ptr_ptr_cv_BP_VAR_W -> _get_zval_cv_lookup_BP_VAR_W -> zend_assign_const_to_variable


variable_ptr_ptr = _get_zval_ptr_ptr_cv_BP_VAR_W(execute_data, opline->op1.var TSRMLS_CC);
该函数原型:
static zend_always_inline zval **_get_zval_ptr_ptr_cv_BP_VAR_W(const zend_execute_data *execute_data, zend_uint var TSRMLS_DC)
{
zval ***ptr = EX_CV_NUM(execute_data, var); //这是execute_data的cv第一部分CV[i]的值其实是个指针


if (UNEXPECTED(*ptr == NULL)) {
return _get_zval_cv_lookup_BP_VAR_W(ptr, var TSRMLS_CC);//这是execute_data的cv第一部分CV[i]的值其实是个指针,这个指针真正分配的空间(该空间可能是execute_data的cv分配,也可能是符号表来分配,两者只能选其一,所以代码里可以看到 size_t CVs_size = ZEND_MM_ALIGNED_SIZE(sizeof(zval **) * op_array->last_var * (EG(active_symbol_table) ? 1 : 2));如果没有符号表就需要分配两倍空间)
}
return *ptr;
}
可以看到就是这个函数做了execute_data与op_array之间的关联


总结第一个例子,opcode解析阶段op_array存放的是变量的一些基本信息,opcode的执行阶段execute_data分配空间存放数据,两者联系就是通过opcode的变量位置等




接下来第二个例子函数调用我们关注的是参数的传递,分两部分(仍然忽视掉语法解析直接到达opcode生成),1 函数解析 2 函数调用:
function foo($arg1)
{
    print($arg1);
}


$bar = 'hello php';
foo($bar);


函数解析:
zend_do_begin_function_declaration(进行CG(active_op_array)的切换,并且将新的函数注册到CG(function_table)全局变量中) -> zend_do_receive_param -> zend_do_end_function_declaration(退出当前,将之前的op_array重置)


zend_do_receive_param:
(同上对$arg1变量信息进行存储,然后每一个参数就会调用一次receive)
该函数利用到了op_array的两个属性num_args与arg_info


CG(active_op_array)->num_args++; 作用显而易见
以及cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];
将参数信息进行存储,将来用来进行参数类型的对比检查


函数调用:
zend_do_begin_function_call -->  zend_do_pass_param() --> zend_do_end_function_call


zend_do_begin_function_call:
函数解析时曾经注册到函数表,此函数会判断函数名是否存在
获取函数后,会将函数压入堆栈,方便处理下面容易获取函数本身
zend_stack_push(&CG(function_call_stack), (void *) &function, sizeof(zend_function *));


zend_do_pass_param:
会将$bar如同前面介绍的一样进行变量处理,然后生成
op->handle=ZEND_SEND_VAR_NO_REF


在解析opcode时调用的是ZEND_SEND_VAR_NO_REF_SPEC_CV_HANDLER函数:
该函数如同上面介绍一样分配了$bar的存储空间,然后需要注意的是zend_vm_stack_push(varptr TSRMLS_CC);
用到了zend_excute结构中的堆栈,将函数空间的指针压入到了堆栈中。


zend_do_end_function_call:
op->handle=ZEND_DO_FCALL_BY_NAME


在解析opcode时调用的是ZEND_DO_FCALL_BY_NAME_SPEC_HANDLER函数:
会将参数的数量压入到堆栈中,如同c的函数调用压入参数一样,arg1,arg2,argnum
zend_vm_stack_push_args(num_args TSRMLS_CC);//注意先压入参数才切换op_array
然后zend_execute(op_array);
调用完后回到本函数调用 zend_vm_stack_clear_multiple进行堆栈释放


从这一步开始进入了函数中,还记得函数的zend_do_receive_param中的op->handle=ZEND_RECV_SPEC_HANDLER:

zval **param = zend_vm_stack_get_arg(arg_num TSRMLS_CC);

static zend_always_inline zval** zend_vm_stack_get_arg(int requested_arg TSRMLS_DC)
{
return zend_vm_stack_get_arg_ex(EG(current_execute_data)->prev_execute_data, requested_arg);
}

为什么要用prev_execute_data因为函数压栈是在当前excute_data之前的excute_data完成,
实际上,在真正执行函数之前,php会将参数个数入栈。


先根据参数个数把堆栈中的参数列表取出来,然后进行参数验证,
顺便var_ptr = _get_zval_ptr_ptr_cv_BP_VAR_W(execute_data, opline->result.var TSRMLS_CC);
将函数的参数变量$arg1进行赋值


整个过程结束



上面是拆分讲解一个函数的调用过程,当将所有程序解析成op_array数组后,就会调用execute_ex来执行所有的opcode数组。

zend_execute_scripts -> zend_execute -> zend_execute_ex -> execute_ex -> i_create_execute_data_from_op_array 


	if (0) {
zend_vm_enter:
		execute_data = i_create_execute_data_from_op_array(EG(active_op_array), 1 TSRMLS_CC);
	}

	LOAD_REGS();
	LOAD_OPLINE();

	while (1) {
    	int ret;
#ifdef ZEND_WIN32
		if (EG(timed_out)) {
			zend_timeout(0);
		}
#endif

		if ((ret = OPLINE->handler(execute_data TSRMLS_CC)) > 0) {
			switch (ret) {
				case 1:
					EG(in_execution) = original_in_execution;
					return;
				case 2:
					goto zend_vm_enter;
					break;
				case 3:
					execute_data = EG(current_execute_data);
					break;
				default:
					break;
			}
		}

	}
	zend_error_noreturn(E_ERROR, "Arrived at end of main loop which shouldn't happen");
}
以上是个死循环,解析op_array数组,需要注意的是返回值
 
  
#define ZEND_VM_CONTINUE()         return 0
#define ZEND_VM_RETURN()           return 1   返回return是函数终止,
#define ZEND_VM_ENTER()            return 2  函数调用
#define ZEND_VM_LEAVE()            return 3  函数退出
 
  
ZEND_VM_RETURN函数返回returen终止,5.2版本很少有返回1,但是5.3增加了yield调用后,yield调用的opcode基本上都会返回1,从而
函数终止,有个疑问,return了后下面如何执行?
这里面有个需要注意的点就是,当我们调用函数的时候大家知道opcode解析的函数是
zend_do_fcall_common_helper_SPEC 
该函数分为两部分,
 
  
typedef union _zend_function {
zend_uchar type; /* ...﹚... #define ZEND_USER_FUNCTION 2
MUST be the first element of this struct! */
struct {
zend_uchar type; /* never used */
char *function_name; //ㄧ..
zend_class_entry *scope; //ㄧ.┮..办
zend_uint fn_flags; // ..猭....单ZEND_ACC_STATIC单
union _zend_function *prototype; //ㄧ.
zend_uint num_args; //....
zend_uint required_num_args; //惠璶....
zend_arg_info *arg_info; //..獺.
zend_bool pass_rest_by_reference;
unsigned char return_reference; //
} common;
zend_op_array op_array; //ㄧ.い巨
zend_internal_function internal_function;
} zend_function;


1 内部C函数(ZEND_INTERNAL_FUNCTION):内部函数在zend_register_functions时候就注册到了函数表,其中internal_function.handler指向C函数(函数指针)
通过opcode解析函数名到函数表中查找即可获取到函数指针,进行调用
2 php函数(ZEND_USER_FUNCTION):会继续调用zend_execute,所以刚才说的ZEND_VM_RETURN终止的只是具体某个函数而已,大家如果在一个函数
中写yield,函数就不会继续执行了,就是这个道理。
 
  
 
  




顺带着介绍下词法解析过程

Zend/zend_language_scanner.l  词法解析规则文件
Zend/zend_language_parser.y   语法分析规则文件

bison -o zend_language_parser.c zend_language_parser.y

在Zend目录下就会生成语法解析器zend_language_parser.c。
yyarse ()是 bison 生成的分析器的主函数。   调用 yyarse() ,如果一切顺利,那么上例中的 g_root 将指向一个完成的语法树。
#define yylex zendlex
#define YYSTYPE znode
yyparse -> zendlex -> lex_scan
lex_scan会解析 YYSTYPE字段,并且将znode.u.constant进行词法解析赋值数据

语法扫描(lex_scan)前都会进行该函数调用进行准备,可以参考函数token_get_all的实现

static void yy_scan_buffer(char *str, unsigned int len TSRMLS_DC)
{
	YYCURSOR = (YYCTYPE*)str;
	SCNG(yy_start) = YYCURSOR;
	YYLIMIT  = YYCURSOR + len;
}


 
  

顺便说一下php7:

 
  
compile_file -> zendparse(yyparse) -> zend_compile_top_stmt -> zend_compile_stmt 生成opcode,因为php7中间用了抽象语法树,
需要根据抽象语法树的节点进行分析后获得最的opcode
 
  
 
  
 
  
在这里补充下一个知识点:
 
  
ZEND_ASSIGN_ADD_SPEC_VAR_CONST_HANDLER 
 
  
	if (RETURN_VALUE_USED(opline)) {
		PZVAL_LOCK(*var_ptr);
		EX_T(opline->result.var).var.ptr = *var_ptr;
	}
这里放的是临时变量的var属性ptr,为什么是指针,而不是下面的tmp_var,是因为ptr指向的zval是不能立马释放的,是需要assign
赋值给其他变量用,也就是多个*zal 共同指向的结构,这个时候采用的就是存放到临时变量的var中的ptr属性。
再看个例子
 
  
 
  
 
  
 
  
ZEND_ADD_SPEC_CV_TMP_HANDLER
 
  
static int ZEND_FASTCALL  ZEND_ADD_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	USE_OPLINE
	zend_free_op free_op2;

	SAVE_OPLINE();
	fast_add_function(&EX_T(opline->result.var).tmp_var,
		_get_zval_ptr_cv_BP_VAR_R(execute_data, opline->op1.var TSRMLS_CC),
		_get_zval_ptr_tmp(opline->op2.var, execute_data, &free_op2 TSRMLS_CC) TSRMLS_CC);

	zval_dtor(free_op2.var);
	CHECK_EXCEPTION();
	ZEND_VM_NEXT_OPCODE();
}
很明显放到了赋值给了临时变量,为什么是临时变量,因为该变量不引用其他指针数据,所以释放比较简单

 
  
 
  
a++的opcode
 
  
 
  
zend_do_post_incdec 的opcode opline->result_type = IS_TMP_VAR;很明显是个tmp变量

 
  
	opline = get_next_op(CG(active_op_array) TSRMLS_CC);
	opline->opcode = op;
	SET_NODE(opline->op1, op1);
	SET_UNUSED(opline->op2);
	opline->result_type = IS_TMP_VAR;
	opline->result.var = get_temporary_variable(CG(active_op_array));
	GET_NODE(result, opline->result);

 
  
 
  
static int ZEND_FASTCALL  ZEND_POST_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
	USE_OPLINE
	zend_free_op free_op1;
	zval **var_ptr, *retval;

	SAVE_OPLINE();
	var_ptr = _get_zval_ptr_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC);

	if (IS_VAR == IS_VAR && UNEXPECTED(var_ptr == NULL)) {
		zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets");
	}
	if (IS_VAR == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) {
		ZVAL_NULL(&EX_T(opline->result.var).tmp_var);
		if (free_op1.var) {zval_ptr_dtor_nogc(&free_op1.var);};
		CHECK_EXCEPTION();
		ZEND_VM_NEXT_OPCODE();
	}

	retval = &EX_T(opline->result.var).tmp_var;
	ZVAL_COPY_VALUE(retval, *var_ptr);
	zendi_zval_copy_ctor(*retval);
当返回的是tmp_var变量时,说明该变量只是个临时值不会做额外的操作,所以也不需要增加gc_recount++等操作,直接堆栈释放即可。
 
  
 
  

++a的opcode:opline->result_type = IS_VAR; IS_VAR类型

	opline = get_next_op(CG(active_op_array) TSRMLS_CC);
	opline->opcode = op;
	SET_NODE(opline->op1, op1);
	SET_UNUSED(opline->op2);
	opline->result_type = IS_VAR;
	opline->result.var = get_temporary_variable(CG(active_op_array));
	GET_NODE(result, opline->result);


解析后:

	if (RETURN_VALUE_USED(opline)) {
		PZVAL_LOCK(*var_ptr);
		EX_T(opline->result.var).var.ptr = *var_ptr;
	}

此时返回的var的临时变量增加了gc_recount++,所以该返回值被别人用的时候,就需要有个释放的过程,

static zend_always_inline zval **_get_zval_ptr_ptr_var(zend_uint var, const zend_execute_data *execute_data, zend_free_op *should_free TSRMLS_DC)
{
	zval** ptr_ptr = EX_T(var).var.ptr_ptr;

	if (EXPECTED(ptr_ptr != NULL)) {
		PZVAL_UNLOCK(*ptr_ptr, should_free);
	} else {
		/* string offset */
		PZVAL_UNLOCK(EX_T(var).str_offset.str, should_free);
	}
	return ptr_ptr;
}
获取该变量值通过该函数,所以就会进行释放gc_recount--.

var和tmp类型的区别是什么,大家都是放在tmp分配的堆栈中,区别就是,var.ptr_ptr是个指针,为了节省空间大家目前先共用,比如++a,返回的临时变量和变量a返回值一样,所以就用指针指向同一个zval,比如a++,返回值的临时变量和变量a返回值不一样,所以必须重新申请一个zval,所以就干脆扔到了tmp中,可以随时释放。



你可能感兴趣的:(php的op_array与execute_data的关系)