函数返回阶段

3.3.3.5 函数返回阶段

实际此过程可以认为是3.3.3.4的一部分,这个阶段就是函数调用结束,返回调用处的过程,这个过程中有三个关键工作:拷贝返回值、执行器切回调用位置、释放清理局部变量。

上面例子此过程opcode为ZEND_RETURN,对应的handler为ZEND_RETURN_SPEC_CV_HANDLER

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RETURN_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval *retval_ptr;
    zend_free_op free_op1;

    //获取返回值
    retval_ptr = _get_zval_ptr_cv_undef(execute_data, opline->op1.var);
    if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
        //返回值未定义,返回NULL
        retval_ptr = GET_OP1_UNDEF_CV(retval_ptr, BP_VAR_R);
        if (EX(return_value)) {
            ZVAL_NULL(EX(return_value));
        }
    } else if(!EX(return_value)){
        //无返回值
        ...
    }else{ //返回值正常
        ...

        ZVAL_DEREF(retval_ptr); //如果retval_ptr是引用则将找到其具体引用的zval
        ZVAL_COPY(EX(return_value), retval_ptr); //将返回值复制给调用方接收值:EX(return_value)
        ...
    }
    ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}

继续看下zend_leave_helper_SPEC,执行器切换、局部变量清理就是在这个函数中完成的。

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_execute_data *old_execute_data;
    uint32_t call_info = EX_CALL_INFO();

    if (EXPECTED(ZEND_CALL_KIND_EX(call_info) == ZEND_CALL_NESTED_FUNCTION)) {
        //普通的函数调用将走到这个分支

        i_free_compiled_variables(execute_data);
        ...
    }
    //include、eval及整个脚本的结束(main函数)走到下面
    //...

    //将执行器切回调用的位置
    EG(current_execute_data) = EX(prev_execute_data);
}

//zend_execute.c
//清理局部变量的过程
static zend_always_inline void i_free_compiled_variables(zend_execute_data *execute_data)
{
    zval *cv = EX_VAR_NUM(0);
    zval *end = cv + EX(func)->op_array.last_var;
    while (EXPECTED(cv != end)) {
        if (Z_REFCOUNTED_P(cv)) {
            if (!Z_DELREF_P(cv)) { //引用计数减一后为0
                zend_refcounted *r = Z_COUNTED_P(cv);
                ZVAL_NULL(cv);
                zval_dtor_func_for_ptr(r); //释放变量值
            } else {
                GC_ZVAL_CHECK_POSSIBLE_ROOT(cv); //引用计数减一后>0,启动垃圾检查机制,清理循环引用导致无法回收的垃圾
            }
        }
        cv++;
    }
}

除了函数调用完成时有return操作,其它还有两种情况也会有此过程:

  • 1.PHP主脚本执行结束时: 也就是PHP脚本开始执行的入口脚本(PHP没有显式的main函数,这种就可以认为是main函数),但是这种情况并不会在return时清理,因为在main函数中定义的变量并非纯碎的局面变量,它们都是全局变量,与$GET、$POST是一类,这些全局变量的清理是在request_shutdown阶段处理
  • 2.include、eval: 以include为例,如果include的文件中定义了全局变量,那么这些变量实际与上面1的情况一样,它们的存储位置是在一起的

所以实际上面说的这两种情况属于一类,它们并不是局部变量的清理,而是 全局变量的清理 ,另外局部变量的清理也并非只有return一个时机,另外还有一个更重要的时机就是变量分离时,这种情况我们在《PHP语法实现》一节再具体说明。

联系我们

邮箱 626512443@qq.com
电话 18611320371(微信)
QQ群 235681453

Copyright © 2015-2024

备案号:京ICP备15003423号-3