奥门新浦京官方网站深入理解PHP内核(六)函数的定义、传参及返回值

在付出进度中,函数的回来值类型应该是规定不改变的,但PHP是弱类型的语言,

意气风发、函数的概念

由此PHP是一贯不此类语法验证的,正因为那样,形成了好些个坑坑。

  客户函数的定义从function 关键字开首,如下

诸如下边包车型地铁代码:

function foo($var) {
    echo $var;
}

<?php
function getArticles(…){
$arrData = array();
if($exp1){
return $arrData;
}else if($exp2){
return 1;
}else{
return false;
}

}
$arrData =getArticles(…);
foreach($arrData as $record){
//do something.
….
}
?>

  1、词法解析

函数getArticles依照差异的尺度再次回到分歧档期的顺序的值,有bool、int、还只怕有数组,寻常意况那类函数是愿意回到数组,然后拿数组去做一些别样操作,

奥门新浦京官方网站,  在Zend/zend_language_scanner.l中大家找到如下所示的代码:

可因为函数再次回到值类型不牢固,调用时就很恐怕产生各类预想不到的坑,

<ST_IN_SCRIPTING>"function" {
    return T_FUNCTION;
}

据此笔者就想,既然不可能正式,那直接强制好了。

  它所代表的意义是function将会生成T_FUNCTION标志。在获得那些符号后,大家开首语法剖析。

函数/方法重返值能够强迫类型,如 图

  2、语法解析

奥门新浦京官方网站 1

  在Zend/zend_language_parser.y文件中找到函数的扬言进程标识如下:

支撑各个强迫类型约束:int、array、bool、object,当再次来到值与函数评释中的类型不相称时,抛出warning,本来想抛出error,不过感觉

function:
    T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }
;

is_reference:
        /* empty */ { $$.op_type = ZEND_RETURN_VAL; }
    |   '&'         { $$.op_type = ZEND_RETURN_REF; }
;

unticked_function_declaration_statement:
        function is_reference T_STRING {
zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }
            '(' parameter_list ')' '{' inner_statement_list '}' {
                zend_do_end_function_declaration(&$1 TSRMLS_CC); }
;

太惨酷了,只好算是个特别,不能算错误,所以就用warning好了。

    关注点在function is_reference
T_STCRUISERING,表示function关键字,是或不是引用,函数名

PHP自己是不扶植 int function
那样的语法的,所以要帮忙,就先要消除语法深入分析器,关于语法深入分析器,能够运动这里>>>查看

  T_FUNCTION标志只是用来恒定函数的宣示,表示那是二个函数,而越来越多的劳作是与这些函数相关的东西,包括参数,重回值。

详细情况,这里就不讲了,

  3、生成中间代码

先校勘语法扫描 Zend/zend_language_scanner.l文件

  语法深入深入分析后,我们看见所实行编写翻译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其完成如下:

追加如下代码:

void zend_do_begin_function_declaration(znode *function_token, znode 
*function_name,
 int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{ 
*/
{
    ...//省略
    function_token->u.op_array = CG(active_op_array);
    lcname = zend_str_tolower_dup(name, name_len);

    orig_interactive = CG(interactive);
    CG(interactive) = 0;
    init_op_array(&op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE 
TSRMLS_CC);
    CG(interactive) = orig_interactive;

     ...//省略

    if (is_method) {
        ...//省略,类方法 在后面的章节介绍
„!ǶGH
    } else {
        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);


        opline->opcode = ZEND_DECLARE_FUNCTION;
        opline->op1.op_type = IS_CONST;
        build_runtime_defined_function_key(&opline->op1.u.constant, lcname,
            name_len TSRMLS_CC);
        opline->op2.op_type = IS_CONST;
        opline->op2.u.constant.type = IS_STRING;
        opline->op2.u.constant.value.str.val = lcname;
        opline->op2.u.constant.value.str.len = name_len;
        Z_SET_REFCOUNT(opline->op2.u.constant, 1);
        opline->extended_value = ZEND_DECLARE_FUNCTION;
        zend_hash_update(CG(function_table), opline-
>op1.u.constant.value.str.val,
            opline->op1.u.constant.value.str.len, &op_array, 
sizeof(zend_op_array),
             (void **) &CG(active_op_array));
    }

}
/* }}} */

<ST_IN_SCRIPTING>”int” {
return T_FUNCTION_RETURN_INT;
}
<ST_IN_SCRIPTING>”bool” {
return T_FUNCTION_RETURN_OBJECT;
}
<ST_IN_SCRIPTING>”object” {
return T_FUNCTION_RETURN_OBJECT;
}
<ST_IN_SCRIPTING>”resource” {
return T_FUNCTION_RETURN_RESOURCE;
}

  生成的代码为ZEND_DECLARE_FUNCTION,依照这些当中的代码及操作数对应的op_type。我们能够找到中间代码的进行函数为ZEND_DECLARE_FUNCTION_SPEC_HANDLER。

乐趣很简短,扫描器扫描到到重点字
int、bool、object、resource、array时回来相应的T_FUNCTION_*
,那是三个token,

    在扭转中间代码的时候,能够看来曾经济同盟并了函数名全副为小写,表示函数的称呼不是区  分大小写的。

scanner遵照区别的token做分歧的处理,token要先在Zend/zend_language_parser.y文件中定义

  为验证那些达成,咱们看风流倜傥段代码

追加如下代码

function T() {
    echo 1;
}

function t() {
    echo 2;
}

……….
%token T_FUNCTION_RETURN_INT
%token T_FUNCTION_RETURN_BOOL
%token T_FUNCTION_RETURN_STRING
%token T_FUNCTION_RETURN_OBJECT
%token T_FUNCTION_RETURN_RESOURCE
1

下一场扩大token管理逻辑:

1
function:
T_FUNCTION { $$.u.opline_num = CG(zend_lineno);$$.u.EA.var  = 0;
}
|   T_FUNCTION_RETURN_INT T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_LONG;
}
|   T_FUNCTION_RETURN_BOOL T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_BOOL;
}
|   T_FUNCTION_RETURN_STRING T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_STRING;
}
|   T_FUNCTION_RETURN_OBJECT T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_OBJECT;
}
|   T_FUNCTION_RETURN_RESOURCE T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_RESOURCE;
}
|   T_ARRAY T_FUNCTION {
$$.u.opline_num = CG(zend_lineno);
$$.u.EA.var = IS_ARRAY;
}

  实行代码会报错Fatal error: Cannot redeclare t(卡塔尔(قطر‎ (previously declared
in …State of Qatar

$$.u.EA.var 存款和储蓄的是 函数重返类型,最终要拿他来跟回来值类型做合作,

  代表对此PHP来讲T和t是同三个函数名,校验函数名是不是再次,那些进程是在哪举行的呢?

像这种类型语法解释器就能够拍卖大家新的php语法了。

  4、实施中间代码

那还缺乏,还需求校正函数宣称定义的拍卖逻辑

  在Zend/zend_vm_execute.h文件中找到ZEND_DECLARE_FUNCTION中间代码对应的实施函数:ZEND_DECLARE_FUNCTION_SPEC_HANDLERAV4。此函数只调用了函数do_bind_function。其调用代码为:

Zend/zend_compile.c ::zend_do_begin_function_declaration

……
zend_op_array op_array;
char *name = function_name->u.constant.value.str.val;
int name_len = function_name->u.constant.value.str.len;
int function_type  = function_token->u.EA.var;
//保存函数类型,在语法解释器中追加的: $$.u.EA.var = IS_LONG;
int function_begin_line = function_token->u.opline_num;
……
op_array.function_name = name;
op_array.fn_type = function_type; //将类型保存到op_array中,
op_array.return_reference = return_reference;
op_array.fn_flags |= fn_flags;
op_array.pass_rest_by_reference = 0;
……….

do_bind_function(EX(opline), EG(function_table), 0);

PHP是先剖判PHP语法生成对应的opcode,将索要的条件、参数新闻保存到execute_data全局变量中,最终在通过execute函数逐个实行opcode,

  在这里个函数上将EX(opline卡塔尔所指向的函数增添到EG(function_table卡塔尔中,并认清是或不是早就存在雷同名字的函数,如果存在则报错,EG(function_table卡塔尔用来寄放推行进度中任何的函数消息,约等于函数的注册表。它的组织是三个HashTable,所以在do_bind_function函数中增添新的函数使用的是HashTable的操作函数zend_hash_add

于是要做处理就要把函数的品类保存到opcode中:op_array.fn_type =
function_type;

 

op_array是没有fn_type的,要修改op_array的结构,增加zend_uint
fn_type;

二、函数的参数

(关于opcode你能够想像一下
从c转为汇编,作者博客中也许有相关小说,能够参见一下)

  函数的定义只是四个将函数名注册到函数列表的长河。

末尾要改正opcode的毁掉函数,函数的回到 return 会生成token
T_RETURN,T_RETUXC60N会依据重临的品类调用不相同的calback函数:

  1、顾客自定义函数的参数

ZEND_RETURN_SPEC_CONST_HANDLER
ZEND_RETURN_SPEC_TMP_HANDLER
ZEND_RETURN_SPEC_VAR_HANDLER

  我们精晓对于函数的参数检查是经过zend_do_receive_arg函数来落到实处的,在那函数中对于参数的机要代码如下:

它有八个callback,要是重回值是叁个 const类型的数额,则
ZEND_RETURN_SPEC_CONST_HANDLER
重回值是有的时候数据,如 : return 1,则ZEND_RETURN_SPEC_TMP_HANDLER
重回值是一个变量,如 : return $a,则ZEND_RETURN_SPEC_VAR_HANDLER

CG(active_op_array)->arg_info = erealloc(CG(active_op_array)->arg_info,
        sizeof(zend_arg_info)*(CG(active_op_array)->num_args));
cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];
cur_arg_info->name = estrndup(varname->u.constant.value.str.val,
        varname->u.constant.value.str.len);
cur_arg_info->name_len = varname->u.constant.value.str.len;
cur_arg_info->array_type_hint = 0;
cur_arg_info->allow_null = 1;
cur_arg_info->pass_by_reference = pass_by_reference;
cur_arg_info->class_name = NULL;
cur_arg_info->class_name_len = 0;

故此要在此多少个callback函数中加进拍卖逻辑:

  整个参数的传递是因此给中间代码的arg_info字段推行赋值操作完毕。关键点是在arg_info字段,arg_info字段的结构如下:

在callback函数return以前扩大如下代码

typedef struct _zend_arg_info {
    const char *name;   /*参数的名称*/
    zend_uint name_len;     /*参数名称的长度*/
    const char *class_name; /* 类名*/
     zend_uint class_name_len;   /*类名长度*/
    zend_bool array_type_hint;  /*数组类型提示*/
    zend_bool allow_null;   /*是否允许为NULLͺ*/
    zend_bool pass_by_reference;    /*是否引用传递*/
    zend_bool return_reference; 
    int required_num_args;  
} zend_arg_info;

if((EG(active_op_array)->fn_type > 0) &&
Z_TYPE_P(retval_ptr) != EG(active_op_array)->fn_type){
php_error_docref0(NULL TSRMLS_DC,E_WARNING, “function name %s
return a wrong type.”, EG(active_op_array)->function_name );
}

  参数的值传递和参数字传送递的分别是透过pass_by_reference参数在变化中间代码时贯彻的。

fn_type 去跟 重回值的品种作比较,若无相配到,就能抛出那些warning。

  对于参数的个数,中间代码中满含的arg_nums字段在每一趟施行**zend_do_receive_argxx时都会加1.之类代码:

自个儿早就打了补丁,方今只帮助php5.3版本,有必要的能够拿去玩大器晚成玩。

CG(active_op_array)->num_args++;

不亮堂为何官方不扶持此语法,笔者以为依旧挺有不能缺乏的。

  何况当前参数的目录为ŒCG(active_op_array)->num_args-1.如下代码:

下载补丁:php-syntax.patch

cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];

  以上的解析是指向函数定义时的参数设置,那一个参数是一直的。而在实质上编写程序时或者大家会用到可变参数。那时大家会用到函数func_num_args和func_get_args。它们是以中间函数存在。于是在Zendzend_builtin_functions.c文件中找到那四个函数的兑现。大家率先来看func_num_args函数的贯彻,其代码如下:

/* {{{ proto int func_num_args(void)
   Get the number of arguments that were passed to the function */
ZEND_FUNCTION(func_num_args)
{
    zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;

    if (ex && ex->function_state.arguments) {
        RETURN_LONG((long)(zend_uintptr_t)*(ex->function_state.arguments));
    } else {
        zend_error(E_WARNING,
"func_num_args():  Called from the global scope - no function context");
        RETURN_LONG(-1);
    }
}
/* }}} */

  在存在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函数为例,其参数管理部分的代码如下:

/* {{{ proto int count(mixed var [, int mode])
   Count the number of elements in a variable (usually an array) */
PHP_FUNCTION(count)
{
    zval *array;
    long mode = COUNT_NORMAL;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l",
         &array, &mode) == FAILURE) {
        return;
    }
    ... //省略
}

  这里包蕴了多个操作:三个是取参数的个数,四个是深入分析参数列表。

  (1State of Qatar取参数的个数

  取参数的个数是透过ZEND_NUM_ATiggoGS(State of Qatar宏来实现的,其定义如下:

#define ZEND_NUM_ARGS()     (ht)

  ht是在Zend/zend.h文件中定义的宏INTE奇骏NAL_FUNCTION_PARAMETERS中的ht,如下

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value,
zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

  (2卡塔尔深入分析参数列表

  PHP内部函数在分条析理参数时使用的是zend_parse_parameters。它能够大大简化参数的选拔管理专门的学问,固然它在管理可变参数时还应该有一点弱。

  其声称如下:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, 
...)
  • 第多少个参数num_args申明表示想要选拔的参数个数,我们平日应用ZEND_NUM_A途胜GS(State of Qatar来表示对传播的参数“有稍许要稍稍”
  • 其次个参数应该是宏TSRMLS_CC。
  • 其多个参数type_spec是贰个字符串,用来钦赐大家所希望采用的生龙活虎黄金年代参数的档期的顺序,有一点相仿于printf中钦点输出格式的那三个格式化字符串。
  • 剩余的参数就是大家用来接过PHP参数值的变量的指针。

  zend_parse_parameters(卡塔尔(قطر‎在解析参数的还要户尽恐怕的转移参数类型,那样就能够有限支持我们总是能获得所愿意的品类的变量

 

  3、函数的再次回到值

  PHP中等高校函授数都有再次回到值,没return重临null

  (1)return语句

  从Zend/zend_language_parser.y文件中得以料定其变动中间代码调用的是zend_do_return函数。

void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) /* {{{ */
{
    zend_op *opline;
    int start_op_number, end_op_number;
 if (do_end_vparse) {
        if (CG(active_op_array)->return_reference
                && !zend_is_function_or_method_call(expr)) {
            zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC);/* 处理返回引用 */
        } else {
            zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC);/* 处理常规变量返回 */
        }
    }

   ...// 省略,取其他中间代码操作

    opline->opcode = ZEND_RETURN;

    if (expr) {
        opline->op1 = *expr;

        if (do_end_vparse && zend_is_function_or_method_call(expr)) {
            opline->extended_value = ZEND_RETURNS_FUNCTION;
        }
    } else {
        opline->op1.op_type = IS_CONST;
        INIT_ZVAL(opline->op1.u.constant);
    }

    SET_UNUSED(opline->op2);
}
/* }}} */

  生成人中学间代码为ZEND_RETU大切诺基N。第二个操作数的花色在再次回到值为可用的表明式时,其体系为表达式的操作类型,不然类型为IS_CONST。那在世襲总计实践中间代码函数时有用到。依据操作数的比不上,ZEND_RETUPRADON中间代码会施行ZEND_RETURN_SPEC_CONST_HANDLER,ZEND_RETURN_SPEC_TMP_HANDLER或ZEND_RETURN_SPEC_TMP_HANDLEQX56。那八个函数的试行流程基本相似,富含对意气风发部分荒诞的拍卖。这里我们以ZEND_RETURN_SPEC_CONST_HANDLE奔驰M级为例表明函数重临值的实践进程:

static int ZEND_FASTCALL  
ZEND_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op *opline = EX(opline);
    zval *retval_ptr;
    zval **retval_ptr_ptr;


    if (EG(active_op_array)->return_reference == ZEND_RETURN_REF) {

        //  ǓǔŷsÁɁƶMļ@ɗÁĻļ
        if (IS_CONST == IS_CONST || IS_CONST == IS_TMP_VAR) {   
            /* Not supposed to happen, but we'll allow it */
            zend_error(E_NOTICE, "Only variable references 
                should be returned by reference");
            goto return_by_value;
        }

        retval_ptr_ptr = NULL;  //  ǓǔŔ

        if (IS_CONST == IS_VAR && !retval_ptr_ptr) {
            zend_error_noreturn(E_ERROR, "Cannot return string offsets by 
reference");
        }
 if (IS_CONST == IS_VAR && !Z_ISREF_PP(retval_ptr_ptr)) {
            if (opline->extended_value == ZEND_RETURNS_FUNCTION &&
                EX_T(opline->op1.u.var).var.fcall_returned_reference) {
            } else if (EX_T(opline->op1.u.var).var.ptr_ptr ==
                    &EX_T(opline->op1.u.var).var.ptr) {
                if (IS_CONST == IS_VAR && !0) {
                      /* undo the effect of get_zval_ptr_ptr() */
                    PZVAL_LOCK(*retval_ptr_ptr);
                }
                zend_error(E_NOTICE, "Only variable references 
                 should be returned by reference");
                goto return_by_value;
            }
        }

        if (EG(return_value_ptr_ptr)) { //  Ǔǔŷs
            SEPARATE_ZVAL_TO_MAKE_IS_REF(retval_ptr_ptr);   //  is_ref__gcőęŒ
1
            Z_ADDREF_PP(retval_ptr_ptr);    //  refcount__gcŒď×1

            (*EG(return_value_ptr_ptr)) = (*retval_ptr_ptr);
        }
    } else {
return_by_value:

        retval_ptr = &opline->op1.u.constant;

        if (!EG(return_value_ptr_ptr)) {
            if (IS_CONST == IS_TMP_VAR) {

            }
        } else if (!0) { /* Not a temp var */
            if (IS_CONST == IS_CONST ||
                EG(active_op_array)->return_reference == ZEND_RETURN_REF ||
                (PZVAL_IS_REF(retval_ptr) && Z_REFCOUNT_P(retval_ptr) > 0)) {
                zval *ret;

                ALLOC_ZVAL(ret);
                INIT_PZVAL_COPY(ret, retval_ptr);   //  Ł™ͿʍǓǔŔ 
                zval_copy_ctor(ret);
                *EG(return_value_ptr_ptr) = ret;
            } else {
                *EG(return_value_ptr_ptr) = retval_ptr; //  ħ6ɶŔ
                Z_ADDREF_P(retval_ptr);
            }
        } else {
            zval *ret;

            ALLOC_ZVAL(ret);
            INIT_PZVAL_COPY(ret, retval_ptr);    //  Ł™ͿʍǓǔŔ 
            *EG(return_value_ptr_ptr) = ret;    
        }
    }

    return zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);   //  Ǔ
ǔĉˆșʒ
}

  函数的重临值在程序实行时存款和储蓄在*EG(return_value_ptr_ptr卡塔尔(قطر‎。ZEND内核对值重临和援用再次回到作了分别,并且在那功底上对常量,有的时候变量和此外品类的变量在回来时作了分歧的管理。在return施行完之后,ZEND内核通过调用zend_leave_helper_SPEC函数,消灭函数内部使用的变量等。那也是ZEND内核自动给函数加上NULL再次回到的原由之生机勃勃。

发表评论

电子邮件地址不会被公开。 必填项已用*标注