深入理解PHP内核(六)函数的定义、传参及返回值,深入理解内核
一、函数的定义
用户函数的定义从function 关键字开始,如下
<span>function</span> foo(<span>$var</span><span>) { </span><span>echo</span> <span>$var</span><span>; }</span>
1、词法分析
在Zend/zend_language_scanner.l中我们找到如下所示的代码:
<ST_IN_SCRIPTING><span>"</span><span>function</span><span>"</span><span> { </span><span>return</span><span> T_FUNCTION; }</span>
它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,我们开始语法分析。
2、语法分析
在Zend/zend_language_parser.y文件中找到函数的声明过程标记如下:
<span>function: T_FUNCTION { $$.u.opline_num </span>=<span> CG(zend_lineno); } ; is_reference: </span><span>/*</span><span> empty </span><span>*/</span> { $$.op_type =<span> ZEND_RETURN_VAL; } </span>| <span>'</span><span>&</span><span>'</span> { $$.op_type =<span> ZEND_RETURN_REF; } ; unticked_function_declaration_statement: function is_reference T_STRING { zend_do_begin_function_declaration(</span>&$<span>1</span>, &$<span>3</span>, <span>0</span>, $<span>2</span><span>.op_type, NULL TSRMLS_CC); } </span><span>'</span><span>(</span><span>'</span> parameter_list <span>'</span><span>)</span><span>'</span> <span>'</span><span>{</span><span>'</span> inner_statement_list <span>'</span><span>}</span><span>'</span><span> { zend_do_end_function_declaration(</span>&$<span>1</span><span> TSRMLS_CC); } ;</span>
关注点在function is_reference T_STRING,表示function关键字,是否引用,函数名
T_FUNCTION标记只是用来定位函数的声明,表示这是一个函数,而更多的工作是与这个函数相关的东西,包括参数,返回值。
3、生成中间代码
语法解析后,我们看到所执行编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其实现如下:
<span>void</span> zend_do_begin_function_declaration(znode *<span>function_token, znode </span>*<span>function_name, </span><span>int</span> is_method, <span>int</span> return_reference, znode *fn_flags_znode TSRMLS_DC) <span>/*</span><span> {{{ </span><span>*/</span><span> { ...</span><span>//</span><span>省略</span> function_token->u.op_array =<span> CG(active_op_array); lcname </span>=<span> zend_str_tolower_dup(name, name_len); orig_interactive </span>=<span> CG(interactive); CG(interactive) </span>= <span>0</span><span>; init_op_array(</span>&<span>op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE TSRMLS_CC); CG(interactive) </span>=<span> orig_interactive; ...</span><span>//</span><span>省略</span> <span>if</span><span> (is_method) { ...</span><span>//</span><span>省略,类方法 在后面的章节介绍</span> !<span>ǶGH } </span><span>else</span><span> { zend_op </span>*opline =<span> get_next_op(CG(active_op_array) TSRMLS_CC); opline</span>->opcode =<span> ZEND_DECLARE_FUNCTION; opline</span>->op1.op_type =<span> IS_CONST; build_runtime_defined_function_key(</span>&opline-><span>op1.u.constant, lcname, name_len TSRMLS_CC); opline</span>->op2.op_type =<span> IS_CONST; opline</span>->op2.u.constant.type =<span> IS_STRING; opline</span>->op2.u.constant.value.str.val =<span> lcname; opline</span>->op2.u.constant.value.str.len =<span> name_len; Z_SET_REFCOUNT(opline</span>->op2.u.constant, <span>1</span><span>); opline</span>->extended_value =<span> ZEND_DECLARE_FUNCTION; zend_hash_update(CG(function_table), opline</span>- ><span>op1.u.constant.value.str.val, opline</span>->op1.u.constant.value.str.len, &<span>op_array, </span><span>sizeof</span><span>(zend_op_array), (</span><span>void</span> **) &<span>CG(active_op_array)); } } </span><span>/*</span><span> }}} </span><span>*/</span><span><br /></span>
生成的代码为ZEND_DECLARE_FUNCTION,根据这个中间的代码及操作数对应的op_type。我们可以找到中间代码的执行函数为ZEND_DECLARE_FUNCTION_SPEC_HANDLER。
在生成中间代码的时候,可以看到已经统一了函数名全部为小写,表示函数的名称不是区 分大小写的。
为验证这个实现,我们看一段代码
<span>function T() { echo </span><span>1</span><span>; } function t() { echo </span><span>2</span><span>; }</span>
执行代码会报错Fatal error: Cannot redeclare t() (previously declared in ...)
表示对于PHP来说T和t是同一个函数名,校验函数名是否重复,这个过程是在哪进行的呢?
4、执行中间代码
在Zend/zend_vm_execute.h文件中找到ZEND_DECLARE_FUNCTION中间代码对应的执行函数:ZEND_DECLARE_FUNCTION_SPEC_HANDLER。此函数只调用了函数do_bind_function。其调用代码为:
do_bind_function(EX(opline), EG(function_table), <span>0</span>);
在这个函数中将EX(opline)所指向的函数添加到EG(function_table)中,并判断是否已经存在相同名字的函数,如果存在则报错,EG(function_table)用来存放执行过程中全部的函数信息,相当于函数的注册表。它的结构是一个HashTable,所以在do_bind_function函数中添加新的函数使用的是HashTable的操作函数zend_hash_add
二、函数的参数
函数的定义只是一个将函数名注册到函数列表的过程。
1、用户自定义函数的参数
我们知道对于函数的参数检查是通过zend_do_receive_arg函数来实现的,在此函数中对于参数的关键代码如下:
CG(active_op_array)->arg_info = erealloc(CG(active_op_array)-><span>arg_info, </span><span>sizeof</span>(zend_arg_info)*(CG(active_op_array)-><span>num_args)); cur_arg_info </span>= &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-<span>1</span><span>]; cur_arg_info</span>->name = estrndup(varname-><span>u.constant.value.str.val, varname</span>-><span>u.constant.value.str.len); cur_arg_info</span>->name_len = varname-><span>u.constant.value.str.len; cur_arg_info</span>->array_type_hint = <span>0</span><span>; cur_arg_info</span>->allow_null = <span>1</span><span>; cur_arg_info</span>->pass_by_reference =<span> pass_by_reference; cur_arg_info</span>->class_name =<span> NULL; cur_arg_info</span>->class_name_len = <span>0</span>;
整个参数的传递是通过给中间代码的arg_info字段执行赋值操作完成。关键点是在arg_info字段,arg_info字段的结构如下:
typedef <span>struct</span><span> _zend_arg_info { </span><span>const</span> <span>char</span> *name; <span>/*</span><span>参数的名称</span><span>*/</span><span> zend_uint name_len; </span><span>/*</span><span>参数名称的长度</span><span>*/</span> <span>const</span> <span>char</span> *class_name; <span>/*</span><span> 类名</span><span>*/</span><span> zend_uint class_name_len; </span><span>/*</span><span>类名长度</span><span>*/</span><span> zend_bool array_type_hint; </span><span>/*</span><span>数组类型提示</span><span>*/</span><span> zend_bool allow_null; </span><span>/*</span><span>是否允许为NULLͺ</span><span>*/</span><span> zend_bool pass_by_reference; </span><span>/*</span><span>是否引用传递</span><span>*/</span><span> zend_bool return_reference; </span><span>int</span><span> required_num_args; } zend_arg_info;</span>
参数的值传递和参数传递的区别是通过pass_by_reference参数在生成中间代码时实现的。
对于参数的个数,中间代码中包含的arg_nums字段在每次执行**zend_do_receive_argxx时都会加1.如下代码:
CG(active_op_array)->num_args++;
并且当前参数的索引为CG(active_op_array)->num_args-1.如下代码:
cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-<span>1</span>];
以上的分析是针对函数定义时的参数设置,这些参数是固定的。而在实际编写程序时可能我们会用到可变参数。此时我们会用到函数func_num_args和func_get_args。它们是以内部函数存在。于是在Zend\zend_builtin_functions.c文件中找到这两个函数的实现。我们首先来看func_num_args函数的实现,其代码如下:
<span>/*</span><span> {{{ proto int func_num_args(void) Get the number of arguments that were passed to the function </span><span>*/</span><span> ZEND_FUNCTION(func_num_args) { zend_execute_data </span>*ex = EG(current_execute_data)-><span>prev_execute_data; </span><span>if</span> (ex && ex-><span>function_state.arguments) { RETURN_LONG((</span><span>long</span>)(zend_uintptr_t)*(ex-><span>function_state.arguments)); } </span><span>else</span><span> { zend_error(E_WARNING, </span><span>"</span><span>func_num_args(): Called from the global scope - no function context</span><span>"</span><span>); RETURN_LONG(</span>-<span>1</span><span>); } } </span><span>/*</span><span> }}} </span><span>*/</span>
在存在ex->function_state.arguments的情况下,及函数调用时,返回ex->function_state.arguments转化后的值,否则显示错误并返回-1。这里最关键的一点是EG(current_execute_data)。这个变量存放的是当前执行程序或函数的数据,此时我们需要取前一个执行程序的数据,为什么呢?因为这个函数的调用是在进入函数后执行的。函数的相关数据等都在之前执行过程中,于是调用的是:
zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;
2、内部函数的参数
以常见的count函数为例,其参数处理部分的代码如下:
<span>/*</span><span> {{{ proto int count(mixed var [, int mode]) Count the number of elements in a variable (usually an array) </span><span>*/</span><span> PHP_FUNCTION(count) { zval </span>*<span>array; </span><span>long</span> mode =<span> COUNT_NORMAL; </span><span>if</span> (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, <span>"</span><span>z|l</span><span>"</span><span>, </span>&array, &mode) ==<span> FAILURE) { </span><span>return</span><span>; } ... </span><span>//</span><span>省略</span> }
这里包括了两个操作:一个是取参数的个数,一个是解析参数列表。
(1)取参数的个数
取参数的个数是通过ZEND_NUM_ARGS()宏来实现的,其定义如下:
<span>#define</span> ZEND_NUM_ARGS() (ht)
ht是在Zend/zend.h文件中定义的宏INTERNAL_FUNCTION_PARAMETERS中的ht,如下
<span>#define</span> INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value,<span> zval </span>**return_value_ptr, zval *this_ptr, <span>int</span> return_value_used TSRMLS_DC
(2)解析参数列表
PHP内部函数在解析参数时使用的是zend_parse_parameters。它可以大大简化参数的接收处理工作,虽然它在处理可变参数时还有点弱。
其声明如下:
ZEND_API <span>int</span> zend_parse_parameters(<span>int</span> num_args TSRMLS_DC, <span>char</span> *<span>type_spec, ...)</span>
- 第一个参数num_args表明表示想要接收的参数个数,我们经常使用ZEND_NUM_ARGS()来表示对传入的参数“有多少要多少”
- 第二个参数应该是宏TSRMLS_CC。
- 第三个参数type_spec是一个字符串,用来指定我们所期待接收的各个参数的类型,有点类似于printf中指定输出格式的那个格式化字符串。
- 剩下的参数就是我们用来接收PHP参数值的变量的指针。
zend_parse_parameters()在解析参数的同时户尽可能的转换参数类型,这样就可以确保我们总是能得到所期望的类型的变量
3、函数的返回值
PHP中函数都有返回值,没return返回null
(1)return语句
从Zend/zend_language_parser.y文件中可以确认其生成中间代码调用的是zend_do_return函数。
<span>void</span> zend_do_return(znode *expr, <span>int</span> do_end_vparse TSRMLS_DC) <span>/*</span><span> {{{ </span><span>*/</span><span> { zend_op </span>*<span>opline; </span><span>int</span><span> start_op_number, end_op_number; </span><span>if</span><span> (do_end_vparse) { </span><span>if</span> (CG(active_op_array)-><span>return_reference </span>&& !<span>zend_is_function_or_method_call(expr)) { zend_do_end_variable_parse(expr, BP_VAR_W, </span><span>0</span> TSRMLS_CC);<span>/*</span><span> 处理返回引用 </span><span>*/</span><span> } </span><span>else</span><span> { zend_do_end_variable_parse(expr, BP_VAR_R, </span><span>0</span> TSRMLS_CC);<span>/*</span><span> 处理常规变量返回 </span><span>*/</span><span> } } ...</span><span>//</span><span> 省略,取其他中间代码操作</span> <span> opline</span>->opcode =<span> ZEND_RETURN; </span><span>if</span><span> (expr) { opline</span>->op1 = *<span>expr; </span><span>if</span> (do_end_vparse &&<span> zend_is_function_or_method_call(expr)) { opline</span>->extended_value =<span> ZEND_RETURNS_FUNCTION; } } </span><span>else</span><span> { opline</span>->op1.op_type =<span> IS_CONST; INIT_ZVAL(opline</span>-><span>op1.u.constant); } SET_UNUSED(opline</span>-><span>op2); } </span><span>/*</span><span> }}} </span><span>*/</span>
生成中间代码为ZEND_RETURN。第一个操作数的类型在返回值为可用的表达式时,其类型为表达式的操作类型,否则类型为IS_CONST。这在后续计算执行中间代码函数时有用到。根据操作数的不同,ZEND_RETURN中间代码会执行ZEND_RETURN_SPEC_CONST_HANDLER,ZEND_RETURN_SPEC_TMP_HANDLER或ZEND_RETURN_SPEC_TMP_HANDLER。这三个函数的执行流程基本类似,包括对一些错误的处理。这里我们以ZEND_RETURN_SPEC_CONST_HANDLER为例说明函数返回值的执行过程:
<span>static</span> <span>int</span><span> ZEND_FASTCALL ZEND_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { zend_op </span>*opline =<span> EX(opline); zval </span>*<span>retval_ptr; zval </span>**<span>retval_ptr_ptr; </span><span>if</span> (EG(active_op_array)->return_reference ==<span> ZEND_RETURN_REF) { </span><span>//</span><span> ǓǔŷsÁ\ɁƶMļ@ɗÁĻļ</span> <span>if</span> (IS_CONST == IS_CONST || IS_CONST ==<span> IS_TMP_VAR) { </span><span>/*</span><span> Not supposed to happen, but we'll allow it </span><span>*/</span><span> zend_error(E_NOTICE, </span><span>"</span><span>Only variable references \</span> should be returned by reference<span>"</span><span>);</span> <span>goto</span><span> return_by_value; } retval_ptr_ptr </span>= NULL; <span>//</span><span> ǓǔŔ</span> <span>if</span> (IS_CONST == IS_VAR && !<span>retval_ptr_ptr) { zend_error_noreturn(E_ERROR, </span><span>"</span><span>Cannot return string offsets by </span> reference<span>"</span><span>);</span> <span> } </span><span>if</span> (IS_CONST == IS_VAR && !<span>Z_ISREF_PP(retval_ptr_ptr)) { </span><span>if</span> (opline->extended_value == ZEND_RETURNS_FUNCTION &&<span> EX_T(opline</span>->op1.u.<span>var</span>).<span>var</span><span>.fcall_returned_reference) { } </span><span>else</span> <span>if</span> (EX_T(opline->op1.u.<span>var</span>).<span>var</span>.ptr_ptr == &EX_T(opline->op1.u.<span>var</span>).<span>var</span><span>.ptr) { </span><span>if</span> (IS_CONST == IS_VAR && !<span>0</span><span>) { </span><span>/*</span><span> undo the effect of get_zval_ptr_ptr() </span><span>*/</span><span> PZVAL_LOCK(</span>*<span>retval_ptr_ptr); } zend_error(E_NOTICE, </span><span>"</span><span>Only variable references \</span> should be returned by reference<span>"</span><span>);</span> <span>goto</span><span> return_by_value; } } </span><span>if</span> (EG(return_value_ptr_ptr)) { <span>//</span><span> Ǔǔŷs</span> SEPARATE_ZVAL_TO_MAKE_IS_REF(retval_ptr_ptr); <span>//</span><span> is_ref__gcőę</span> <span>1</span><span> Z_ADDREF_PP(retval_ptr_ptr); </span><span>//</span><span> refcount__gcŒď×1</span> <span> (</span>*EG(return_value_ptr_ptr)) = (*<span>retval_ptr_ptr); } } </span><span>else</span><span> { return_by_value: retval_ptr </span>= &opline-><span>op1.u.constant; </span><span>if</span> (!<span>EG(return_value_ptr_ptr)) { </span><span>if</span> (IS_CONST ==<span> IS_TMP_VAR) { } } </span><span>else</span> <span>if</span> (!<span>0</span>) { <span>/*</span><span> Not a temp var </span><span>*/</span> <span>if</span> (IS_CONST == IS_CONST ||<span> EG(active_op_array)</span>->return_reference == ZEND_RETURN_REF ||<span> (PZVAL_IS_REF(retval_ptr) </span>&& Z_REFCOUNT_P(retval_ptr) > <span>0</span><span>)) { zval </span>*<span>ret; ALLOC_ZVAL(ret); INIT_PZVAL_COPY(ret, retval_ptr); </span><span>//</span><span> ŁͿʍǓǔŔ </span> <span> zval_copy_ctor(ret); </span>*EG(return_value_ptr_ptr) =<span> ret; } </span><span>else</span><span> { </span>*EG(return_value_ptr_ptr) = retval_ptr; <span>//</span><span> ħ6ɶŔ</span> <span> Z_ADDREF_P(retval_ptr); } } </span><span>else</span><span> { zval </span>*<span>ret; ALLOC_ZVAL(ret); INIT_PZVAL_COPY(ret, retval_ptr); </span><span>//</span><span> ŁͿʍǓǔŔ </span> *EG(return_value_ptr_ptr) =<span> ret; } } </span><span>return</span> zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); <span>//</span><span> Ǔ</span> <span>ǔĉșʒ }</span>
函数的返回值在程序执行时存储在*EG(return_value_ptr_ptr)。ZEND内核对值返回和引用返回作了区别,并且在此基础上对常量,临时变量和其他类型的变量在返回时作了不同的处理。在return执行完之后,ZEND内核通过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。这也是ZEND内核自动给函数加上NULL返回的原因之一。

데이터베이스 스토리지 세션 사용의 주요 장점에는 지속성, 확장 성 및 보안이 포함됩니다. 1. 지속성 : 서버가 다시 시작 되더라도 세션 데이터는 변경되지 않아도됩니다. 2. 확장 성 : 분산 시스템에 적용하여 세션 데이터가 여러 서버간에 동기화되도록합니다. 3. 보안 : 데이터베이스는 민감한 정보를 보호하기 위해 암호화 된 스토리지를 제공합니다.

SessionHandlerInterface 인터페이스를 구현하여 PHP에서 사용자 정의 세션 처리 구현을 수행 할 수 있습니다. 특정 단계에는 다음이 포함됩니다. 1) CustomsessionHandler와 같은 SessionHandlerInterface를 구현하는 클래스 만들기; 2) 인터페이스의 방법 (예 : Open, Close, Read, Write, Despare, GC)의 수명주기 및 세션 데이터의 저장 방법을 정의하기 위해 방법을 다시 작성합니다. 3) PHP 스크립트에 사용자 정의 세션 프로세서를 등록하고 세션을 시작하십시오. 이를 통해 MySQL 및 Redis와 같은 미디어에 데이터를 저장하여 성능, 보안 및 확장 성을 향상시킬 수 있습니다.

SessionId는 웹 애플리케이션에 사용되는 메커니즘으로 사용자 세션 상태를 추적합니다. 1. 사용자와 서버 간의 여러 상호 작용 중에 사용자의 신원 정보를 유지하는 데 사용되는 무작위로 생성 된 문자열입니다. 2. 서버는 쿠키 또는 URL 매개 변수를 통해 클라이언트로 생성하여 보낸다. 3. 생성은 일반적으로 임의의 알고리즘을 사용하여 독창성과 예측 불가능 성을 보장합니다. 4. 실제 개발에서 Redis와 같은 메모리 내 데이터베이스를 사용하여 세션 데이터를 저장하여 성능 및 보안을 향상시킬 수 있습니다.

JWT 또는 쿠키를 사용하여 API와 같은 무국적 환경에서 세션을 관리 할 수 있습니다. 1. JWT는 무국적자 및 확장 성에 적합하지만 빅 데이터와 관련하여 크기가 크다. 2. 쿠키는보다 전통적이고 구현하기 쉽지만 보안을 보장하기 위해주의해서 구성해야합니다.

세션 관련 XSS 공격으로부터 응용 프로그램을 보호하려면 다음 조치가 필요합니다. 1. 세션 쿠키를 보호하기 위해 Httponly 및 Secure 플래그를 설정하십시오. 2. 모든 사용자 입력에 대한 내보내기 코드. 3. 스크립트 소스를 제한하기 위해 컨텐츠 보안 정책 (CSP)을 구현하십시오. 이러한 정책을 통해 세션 관련 XSS 공격을 효과적으로 보호 할 수 있으며 사용자 데이터가 보장 될 수 있습니다.

PHP 세션 성능을 최적화하는 방법 : 1. 지연 세션 시작, 2. 데이터베이스를 사용하여 세션을 저장, 3. 세션 데이터 압축, 4. 세션 수명주기 관리 및 5. 세션 공유 구현. 이러한 전략은 높은 동시성 환경에서 응용의 효율성을 크게 향상시킬 수 있습니다.

THESESSION.GC_MAXLIFETIMESETTINGINSTTINGTINGSTINGTERMINESTERMINESTERSTINGSESSIONDATA, SETINSECONDS.1) IT'SCONFIGUDEDINPHP.INIORVIAINI_SET ()

PHP에서는 Session_Name () 함수를 사용하여 세션 이름을 구성 할 수 있습니다. 특정 단계는 다음과 같습니다. 1. Session_Name () 함수를 사용하여 Session_Name ( "my_session")과 같은 세션 이름을 설정하십시오. 2. 세션 이름을 설정 한 후 세션을 시작하여 세션을 시작하십시오. 세션 이름을 구성하면 여러 응용 프로그램 간의 세션 데이터 충돌을 피하고 보안을 향상시킬 수 있지만 세션 이름의 독창성, 보안, 길이 및 설정 타이밍에주의를 기울일 수 있습니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

맨티스BT
Mantis는 제품 결함 추적을 돕기 위해 설계된 배포하기 쉬운 웹 기반 결함 추적 도구입니다. PHP, MySQL 및 웹 서버가 필요합니다. 데모 및 호스팅 서비스를 확인해 보세요.

SecList
SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

Atom Editor Mac 버전 다운로드
가장 인기 있는 오픈 소스 편집기

에디트플러스 중국어 크랙 버전
작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)
