php控制结构语句declare中的tick的详解[整理版]
先看看手册是怎么说的: declare 结构用来设定一段代码的执行指令。 declare 的语法和其它流程控制结构相: 1 declare (directive) 2 statement directive 部分允许设定 declare 代码段的行为。目前只认识两个指令: ticks (更多信息见下面ticks指令)以及 e
先看看手册是怎么说的:
declare 结构用来设定一段代码的执行指令。declare 的语法和其它流程控制结构相似:
1
|
declare (directive)
|
2
|
statement
|
directive 部分允许设定 declare 代码段的行为。目前只认识两个指令:ticks(更多信息见下面 ticks 指令)以及encoding(更多信息见下面 encoding指令)。
Note:
ticks 指令在 PHP 5.3.0 中是过时指令,将会从 PHP 6.0.0 移除。
encoding 是 PHP 5.3.0 新增指令。
Tick 是一个在 declare 代码段中解释器每执行 N 条低级语句就会发生的事件。N 的值是在 declare 中的 directive部分用 ticks=N
来指定的。
在每个 tick 中出现的事件是由 register_tick_function() 来指定的。更多细节见下面的例子。注意每个
tick 中可以出现多个事件。
看完手册还是觉得云里雾里,再看看别人是怎么描述:
根据代码解析:
01
|
|
02
|
function doTicks
()
|
03
|
{
|
04
|
echo 'Ticks' ;
|
05
|
}
|
06
|
register_tick_function( 'doTicks' );
|
07
|
declare (ticks
= 1) {
|
08
|
for ( $x =
1; $x
|
09
|
echo $x * $x . ' ;
|
10
|
}
|
11
|
}
|
12
|
?>
|
运算结果:
01
|
1
|
02
|
TicksTicks4
|
03
|
TicksTicks9
|
04
|
TicksTicks16
|
05
|
TicksTicks25
|
06
|
TicksTicks36
|
07
|
TicksTicks49
|
08
|
TicksTicks64
|
09
|
TicksTicks81
|
10
|
TicksTicksTicksTicks
|
产生三个疑问:
(1)为什么先输出1之后才输出“Ticks”?
(2)为什么在输出81后还输出四个Ticks ?
(3)declare中的for循环怎么分解成低级语句(low-level)?
这是每个初次接触ticks的人都会碰到的问题。首先register_tick_function函数定义了每个tick事件发生时的处理函数。那么什么是tick事件呢?先看手册上对ticks的解释:
1
|
A
tick is an event that occurs for every
N low-level statements executed by the parser within the declare block.
The value for N
is specified using ticks=N within the declare blocks's
directive div.
|
2
|
The
event(s) that occur on each tick are specified using the register_tick_function().
|
这个解释有三层意思:
(1) tick是一个事件。
(2) tick事件在PHP每执行N条低级语句就发生一次,N由declare语句指定。
(3)可以用register_tick_function()来指定tick事件发生时应该执行的操作。
很明显,理解上面的输出结果最关键的是了解什么是低级语句(low-level
statements),它又是如何进行计数的。我们首先还是将上面的程序通过OPDUMP编译成OPCODEs:
01
|
1:
|
02
|
0
NOP
|
03
|
2:
|
04
|
3: function doTicks
()
|
05
|
4:
{
|
06
|
5: echo 'Ticks' ;
|
07
|
0 ECHO 'Ticks'
|
08
|
6:
}
|
09
|
1
RETURN null
|
10
|
7:
register_tick_function( 'doTicks' );
|
11
|
1
SEND_VAL 'doTicks'
|
12
|
2
DO_FCALL 'register_tick_function' [extval:1]
|
13
|
8: declare (ticks
= 1) {
|
14
|
9: for ( $x =
1; $x
|
15
|
3
ASSIGN !0, 1
|
16
|
4
IS_SMALLER !0, 10 =>RES[~2]
|
17
|
5
JMPZNZ ~2, ->14 [extval:8]
|
18
|
6
PRE_INC !0
|
19
|
7
JMP ->4
|
20
|
10: echo $x * $x . ' ;
|
21
|
8
MUL !0, !0 =>RES[~4]
|
22
|
9
CONCAT ~4, ' =>RES[~5]
|
23
|
10 ECHO ~5
|
24
|
11
TICKS 1 =>RES[]
|
25
|
11:
}
|
26
|
12
TICKS 1 =>RES[]
|
27
|
13
JMP ->6
|
28
|
14
TICKS 1 =>RES[]
|
29
|
12:
}
|
30
|
15
TICKS 1 =>RES[]
|
31
|
16
RETURN 1
|
很明显,PHP的编译过程已经在编译后每条语句的OPCODE序列中插入了TICKS指令用于处理tick事件。那么这些TICKS是根据什么规则来插入的呢?
我们还是从PHP Zend Engine的源代码中寻找答案。
通过简单的文本搜索我们可以知道生成ZEND_TICKS指令的唯一函数是zend_do_ticks(该函数的实现在zend_compile.c中)。
现在再从PHP的语法分析文件zend_language_parser.y(PHP使用bison来做语法分析,所有的语法规则均定义在zend_language_parser.y中)中寻找调用zend_do_ticks的地方。
再一次使用简单的文本搜索,我们可以得到调用zend_do_ticks的三条语法规则:
01
|
statement:
|
02
|
unticked_statement
{ zend_do_ticks(TSRMLS_C); }
|
03
|
|
...
|
04
|
;
|
05
|
function_declaration_statement:
|
06
|
unticked_function_declaration_statement
{ zend_do_ticks(TSRMLS_C); }
|
07
|
;
|
08
|
class_declaration_statement:
|
09
|
unticked_class_declaration_statement
{ zend_do_ticks(TSRMLS_C); }
|
10
|
;
|
也就是说,PHP编译会在statement(语句), function_declaration_statement(函数定义语句), class_declaration_statement后插入TICKS处理函数,即它会在每条statement,函数声明,类(实际上还包括接口)声明后插入一条TICKS指令。
函数与类声明语句比较好理解,根据unticked_function_declaration_statement的语法定义:
1
|
unticked_function_declaration_statement:
|
2
|
function is_reference
T_STRING { zend_do_begin_function_declaration(& $1 ,
& $3 ,
0, $2 .op_type,
NULL TSRMLS_CC); }
|
3
|
'(' parameter_list ')' '{' inner_statement_list '}' {
zend_do_end_function_declaration(& $1 TSRMLS_CC);
}
|
4
|
;
|
同样从unticked_class_declaration_statement的语法定义:
01
|
unticked_class_declaration_statement:
|
02
|
class_entry_type
T_STRING extends_from
|
03
|
{
zend_do_begin_class_declaration(& $1 ,
& $2 ,
& $3 TSRMLS_CC);
}
|
04
|
implements_list
|
05
|
'{'
|
06
|
class_statement_list
|
07
|
'}' {
zend_do_end_class_declaration(& $1 ,
& $2 TSRMLS_CC);
}
|
08
|
|
interface_entry T_STRING
|
09
|
{
zend_do_begin_class_declaration(& $1 ,
& $2 ,
NULL TSRMLS_CC); }
|
10
|
interface_extends_list
|
11
|
'{'
|
12
|
class_statement_list
|
13
|
'}' {
zend_do_end_class_declaration(& $1 ,
& $2 TSRMLS_CC);
}
|
14
|
;
|
最复杂的是statement,它的核心是下面的定义:
01
|
unticked_statement:
|
02
|
'{' inner_statement_list '}'
|
03
|
|
T_IF '(' expr ')' {
zend_do_if_cond(& $3 ,
& $4 TSRMLS_CC);
} statement { zend_do_if_after_statement(& $4 ,
1 TSRMLS_CC); } elseif_list else_single { zend_do_if_end(TSRMLS_C); }
|
04
|
|
T_IF '(' expr ')' ':' {
zend_do_if_cond(& $3 ,
& $4 TSRMLS_CC);
} inner_statement_list { zend_do_if_after_statement(& $4 ,
1 TSRMLS_CC); } new_elseif_list new_else_single T_ENDIF ';' {
zend_do_if_end(TSRMLS_C); }
|
05
|
|
T_WHILE '(' { $1 .u.opline_num
= get_next_op_number(CG(active_op_array)); } expr ')' {
zend_do_while_cond(& $4 ,
& $5 TSRMLS_CC);
} while_statement { zend_do_while_end(& $1 ,
& $5 TSRMLS_CC);
}
|
06
|
|
T_DO { $1 .u.opline_num
= get_next_op_number(CG(active_op_array)); zend_do_do_while_begin(TSRMLS_C); } statement T_WHILE '(' { $5 .u.opline_num
= get_next_op_number(CG(active_op_array)); } expr ')' ';' {
zend_do_do_while_end(& $1 ,
& $5 ,
& $7 TSRMLS_CC);
}
|
07
|
|
T_FOR
|
08
|
'('
|
09
|
for_expr
|
10
|
';' {
zend_do_free(& $3 TSRMLS_CC); $4 .u.opline_num
= get_next_op_number(CG(active_op_array)); }
|
11
|
for_expr
|
12
|
';' {
zend_do_extended_info(TSRMLS_C); zend_do_for_cond(& $6 ,
& $7 TSRMLS_CC);
}
|
13
|
for_expr
|
14
|
')' {
zend_do_free(& $9 TSRMLS_CC);
zend_do_for_before_statement(& $4 ,
& $7 TSRMLS_CC);
}
|
15
|
for_statement
{ zend_do_for_end(& $7 TSRMLS_CC);
}
|
16
|
|
T_SWITCH '(' expr ')' {
zend_do_switch_cond(& $3 TSRMLS_CC);
} switch_case_list { zend_do_switch_end(& $6 TSRMLS_CC);
}
|
17
|
|
T_BREAK ';' {
zend_do_brk_cont(ZEND_BRK, NULL TSRMLS_CC); }
|
18
|
|
T_BREAK expr ';' {
zend_do_brk_cont(ZEND_BRK, & $2 TSRMLS_CC);
}
|
19
|
|
T_CONTINUE ';' {
zend_do_brk_cont(ZEND_CONT, NULL TSRMLS_CC); }
|
20
|
|
T_CONTINUE expr ';' {
zend_do_brk_cont(ZEND_CONT, & $2 TSRMLS_CC);
}
|
21
|
|
T_RETURN ';' {
zend_do_return(NULL, 0 TSRMLS_CC); }
|
22
|
|
T_RETURN expr_without_variable ';' {
zend_do_return(& $2 ,
0 TSRMLS_CC); }
|
23
|
|
T_RETURN variable ';' {
zend_do_return(& $2 ,
1 TSRMLS_CC); }
|
24
|
|
T_GLOBAL global_var_list ';'
|
25
|
|
T_STATIC static_var_list ';'
|
26
|
|
T_ECHO echo_expr_list ';'
|
27
|
|
T_INLINE_HTML { zend_do_echo(& $1 TSRMLS_CC);
}
|
28
|
|
expr ';' {
zend_do_free(& $1 TSRMLS_CC);
}
|
29
|
|
T_UNSET '(' unset_variables ')' ';'
|
30
|
|
T_FOREACH '(' variable
T_AS
|
31
|
{
zend_do_foreach_begin(& $1 ,
& $2 ,
& $3 ,
& $4 ,
1 TSRMLS_CC); }
|
32
|
foreach_variable
foreach_optional_arg ')' {
zend_do_foreach_cont(& $1 ,
& $2 ,
& $4 ,
& $6 ,
& $7 TSRMLS_CC);
}
|
33
|
foreach_statement
{ zend_do_foreach_end(& $1 ,
& $4 TSRMLS_CC);
}
|
34
|
|
T_FOREACH '(' expr_without_variable
T_AS
|
35
|
{
zend_do_foreach_begin(& $1 ,
& $2 ,
& $3 ,
& $4 ,
0 TSRMLS_CC); }
|
36
|
variable
foreach_optional_arg ')' {
zend_check_writable_variable(& $6 );
zend_do_foreach_cont(& $1 ,
& $2 ,
& $4 ,
& $6 ,
& $7 TSRMLS_CC);
}
|
37
|
foreach_statement
{ zend_do_foreach_end(& $1 ,
& $4 TSRMLS_CC);
}
|
38
|
|
T_DECLARE { $1 .u.opline_num
= get_next_op_number(CG(active_op_array)); zend_do_declare_begin(TSRMLS_C); } '(' declare_list ')' declare_statement
{ zend_do_declare_end(& $1 TSRMLS_CC);
}
|
39
|
| ';' /*
empty statement */
|
40
|
|
T_TRY { zend_do_try(& $1 TSRMLS_CC);
} '{' inner_statement_list '}'
|
41
|
T_CATCH '(' {
zend_initialize_try_catch_element(& $1 TSRMLS_CC);
}
|
42
|
fully_qualified_class_name
{ zend_do_first_catch(& $7 TSRMLS_CC);
}
|
43
|
T_VARIABLE ')' {
zend_do_begin_catch(& $1 ,
& $9 ,
& $11 ,
& $7 TSRMLS_CC);
}
|
44
|
'{' inner_statement_list '}' {
zend_do_end_catch(& $1 TSRMLS_CC);
}
|
45
|
additional_catches
{ zend_do_mark_last_catch(& $7 ,
& $18 TSRMLS_CC);
}
|
46
|
|
T_THROW expr ';' {
zend_do_throw(& $2 TSRMLS_CC);
}
|
47
|
|
T_GOTO T_STRING ';' {
zend_do_goto(& $2 TSRMLS_CC);
}
|
48
|
;
|
根据上面的定义,我们知道,statement包括:
(1) 简单语句:空语句(就一个;号),return,break,continue,throw, goto,global,static,unset,echo, 内置的HTML文本,分号结束的表达式等均算一个语句。
(2) 复合语句:完整的if/elseif,while,do...while,for,foreach,switch,try...catch等算一个语句。
(3) 语句块:{} 括出来的语句块。
(4) 最后特别的:declare块本身也算一个语句(按道理declare块也算是复合语句,但此处特意将其独立出来)。
所有的statement, function_declare_statement, class_declare_statement就构成了所谓的低级语句(low-level statement)。
现在再来看开始的例子就比较好理解了:
首先完整的for循环算一个语句,但必须要等循环结束才算,因此在编译时for循环里面的echo 算第一个语句。
所以第一个doTicks是在第一个echo后执行的,也就是1输出后才发生第一个tick事件。
在$x 从1到9的循环中,每个循环包括两个语句,一个echo, 一个for循环。在81输出后,因为echo是一条语句,因此输出第一个ticks。
同时$x=9的这个for循环也结束了,这又是一条语句,输出第二个ticks;开始$x=10的循环,但这时已不满足循环条件,for循环执行结束,这个循环又是一个语句,这时输出第三个ticks。
最后declare本身也算一条语句,所以又输出第四个ticks。
说了半天,ticks到底有什么用?实际上可用tick来进行调试,性能测试,实现简单的多任务,或者做一些后台的I/O操作等等。