建立最簡單的物件(c 原始碼)

coder_study發表於2019-11-04
class Student{}

$s = new Student;

https://3v4l.org/RsEDf/vld#output
對應的 opcode:
NOP
NEW
DO_FCALL
ASSIGN
RETURN

程式編譯完後生成 op_array,編譯階段的最終產物,也是執行階段的輸入;op_array屬性中包含了編譯好的opcodes

列印出的opcodes:

{{handler = 0x555555b4c981 <ZEND_NOP_SPEC_HANDLER>, op1 = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, op2 = {constant = 0, var = 0, num = 0, opline_num = 0,
      jmp_offset = 0}, result = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, extended_value = 0, lineno = 3, opcode = 0 '\000', op1_type = 8 '\b', op2_type = 8 '\b',
    result_type = 8 '\b'}, {handler = 0x555555b4fff2 <ZEND_NEW_SPEC_CONST_HANDLER>, op1 = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, op2 = {constant = 3, var = 3,
      num = 3, opline_num = 3, jmp_offset = 3}, result = {constant = 112, var = 112, num = 112, opline_num = 112, jmp_offset = 112}, extended_value = 0, lineno = 5, opcode = 68 'D',
    op1_type = 1 '\001', op2_type = 8 '\b', result_type = 4 '\004'}, {handler = 0x555555b4a2ac <ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER>, op1 = {constant = 8, var = 8, num = 8, opline_num = 8,
      jmp_offset = 8}, op2 = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, result = {constant = 2, var = 2, num = 2, opline_num = 2, jmp_offset = 2}, extended_value = 0,
    lineno = 5, opcode = 60 '<', op1_type = 8 '\b', op2_type = 8 '\b', result_type = 8 '\b'}, {handler = 0x555555ba3cd2 <ZEND_ASSIGN_SPEC_CV_VAR_RETVAL_UNUSED_HANDLER>, op1 = {constant = 80,
      var = 80, num = 80, opline_num = 80, jmp_offset = 80}, op2 = {constant = 112, var = 112, num = 112, opline_num = 112, jmp_offset = 112}, result = {constant = 3, var = 3, num = 3,
      opline_num = 3, jmp_offset = 3}, extended_value = 0, lineno = 5, opcode = 38 '&', op1_type = 16 '\020', op2_type = 4 '\004', result_type = 8 '\b'}, {
    handler = 0x555555b4f852 <ZEND_RETURN_SPEC_CONST_HANDLER>, op1 = {constant = 32, var = 32, num = 32, opline_num = 32, jmp_offset = 32}, op2 = {constant = 0, var = 0, num = 0,
      opline_num = 0, jmp_offset = 0}, result = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, extended_value = 4294967295, lineno = 6, opcode = 62 '>', op1_type = 1 '\001',
    op2_type = 8 '\b', result_type = 8 '\b'}, {handler = 0xa0, op1 = {constant = 1443046120, var = 1443046120, num = 1443046120, opline_num = 1443046120, jmp_offset = 1443046120}, op2 = {
      constant = 21845, var = 21845, num = 21845, opline_num = 21845, jmp_offset = 21845}, result = {constant = 0, var = 0, num = 0, opline_num = 0, jmp_offset = 0}, extended_value = 0,
    lineno = 570, opcode = 0 '\000', op1_type = 0 '\000', op2_type = 0 '\000', result_type = 0 '\000'}}

ZEND_NOP_SPEC_HANDLER:

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NOP_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE

    ZEND_VM_NEXT_OPCODE();
}
#define ZEND_VM_NEXT_OPCODE() \
    ZEND_VM_NEXT_OPCODE_EX(0, 1)
#define ZEND_VM_NEXT_OPCODE_EX(check_exception, skip) \
    CHECK_SYMBOL_TABLES() \
    if (check_exception) { \
        OPLINE = EX(opline) + (skip); \
    } else { \
        OPLINE = opline + (skip); \
    } \
    ZEND_VM_CONTINUE()

ZEND_NEW_SPEC_CONST_HANDLER:

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_NEW_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zval *result;
    zend_function *constructor;
    zend_class_entry *ce;
    zend_execute_data *call;

    SAVE_OPLINE(); //EX(opline) = opline
    if (IS_CONST == IS_CONST) {
        //讀取快取       根據類名查詢zend_class_entry
        ce = CACHED_PTR(Z_CACHE_SLOT_P(EX_CONSTANT(opline->op1)));
        if (UNEXPECTED(ce == NULL)) {
            ////第1步:根據類名查詢zend_class_entry
            ce = zend_fetch_class_by_name(Z_STR_P(EX_CONSTANT(opline->op1)), EX_CONSTANT(opline->op1) + 1, ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
            if (UNEXPECTED(ce == NULL)) {
                ZEND_ASSERT(EG(exception));
                HANDLE_EXCEPTION();
            }
            CACHE_PTR(Z_CACHE_SLOT_P(EX_CONSTANT(opline->op1)), ce);
        }
    } else if (IS_CONST == IS_UNUSED) {
        ce = zend_fetch_class(NULL, opline->op1.num);
        if (UNEXPECTED(ce == NULL)) {
            ZEND_ASSERT(EG(exception));
            HANDLE_EXCEPTION();
        }
    } else {
        ce = Z_CE_P(EX_VAR(opline->op1.var));
    }

    result = EX_VAR(opline->result.var);
    //2.建立&初始化一個這個類的物件           建立&初始化一個這個類的物件
    if (UNEXPECTED(object_init_ex(result, ce) != SUCCESS)) {   //_object_and_properties_init
        HANDLE_EXCEPTION();
    }
    //第3步:獲取構造方法=== 直接取zend_class_entry.constructor
    //get_constructor => zend_std_get_constructor()

    //#define Z_OBJ_HT(zval)                Z_OBJ(zval)->handlers
    //#define Z_OBJ_HT_P(zval_p)            Z_OBJ_HT(*(zval_p))

    //define Z_OBJ(zval)                    (zval).value.obj
    //#define Z_OBJ_P(zval_p)               Z_OBJ(*(zval_p))

    constructor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));
    if (constructor == NULL) {
        if (UNEXPECTED(EG(exception))) {
            zval_ptr_dtor(result);
            HANDLE_EXCEPTION();
        }

        /* If there are no arguments, skip over the DO_FCALL opcode. We check if the next
         * opcode is DO_FCALL in case EXT instructions are used. */
         //如果沒有引數,則跳過DO_FCALL操作碼。如果使用EXT指令,則檢查下一個操作碼是否為DO_FCALL
        if (EXPECTED(opline->extended_value == 0 && (opline+1)->opcode == ZEND_DO_FCALL)) {
            //此opcode之後還有傳參、呼叫構造方法的操作
            //所以如果沒有定義構造方法則直接跳過這些操作
            ZEND_VM_NEXT_OPCODE_EX(1, 2);
        }

        /* Perform a dummy function call */
        call = zend_vm_stack_push_call_frame(
            ZEND_CALL_FUNCTION, (zend_function *) &zend_pass_function,
            opline->extended_value, NULL, NULL);
    } else {
        if (EXPECTED(constructor->type == ZEND_USER_FUNCTION) && UNEXPECTED(!constructor->op_array.run_time_cache)) {
        //新增執行時方法的快取
            init_func_run_time_cache(&constructor->op_array);
        }
        /* We are not handling overloaded classes right now */
        // 現在一個頁面中不支援類的重新定義的,只支援方法的重寫,覆蓋定義
        //初始化呼叫建構函式的zend_execute_data
        call = zend_vm_stack_push_call_frame(
            ZEND_CALL_FUNCTION | ZEND_CALL_RELEASE_THIS | ZEND_CALL_CTOR,
            constructor,
            opline->extended_value,
            ce,
            Z_OBJ_P(result));
        Z_ADDREF_P(result);
    }

    call->prev_execute_data = EX(call);
    EX(call) = call;
    ZEND_VM_NEXT_OPCODE();
}

1)zend_fetch_class_by_name:

zend_class_entry *zend_fetch_class_by_name(zend_string *class_name, const zval *key, int fetch_type) /* {{{ */
{
    zend_class_entry *ce;

    if (fetch_type & ZEND_FETCH_CLASS_NO_AUTOLOAD) {
        //沒有自動載入
        return zend_lookup_class_ex(class_name, key, 0);
        //有自動載入,
    } else if ((ce = zend_lookup_class_ex(class_name, key, 1)) == NULL) {
        if ((fetch_type & ZEND_FETCH_CLASS_SILENT) == 0 && !EG(exception)) {
            if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_INTERFACE) {
                //介面沒有找到
                zend_throw_or_error(fetch_type, NULL, "Interface '%s' not found", ZSTR_VAL(class_name));
            } else if ((fetch_type & ZEND_FETCH_CLASS_MASK) == ZEND_FETCH_CLASS_TRAIT) {
                //多繼承的一種方式,其中一種沒有找到
                zend_throw_or_error(fetch_type, NULL, "Trait '%s' not found", ZSTR_VAL(class_name));
            } else {
                //類沒有找到
                zend_throw_or_error(fetch_type, NULL, "Class '%s' not found", ZSTR_VAL(class_name));
            }
        }
        return NULL;
    }
    return ce;
}

zend_lookup_class_ex

2) object_init_ex:物件初始化

 ZEND_API int _object_init_ex(zval *arg, zend_class_entry *class_type ZEND_FILE_LINE_DC) /* {{{ */
{
    return _object_and_properties_init(arg, class_type, 0 ZEND_FILE_LINE_RELAY_CC);
}

這個函式需要“properties”包含類中宣告的所有屬性,並且所有屬性都是公共的。如果只有一個子集是給定的,或者類有受保護的成員,那麼您需要透過呼叫zend_merge_properties()來分別合併屬性。

//初始化物件的操作
ZEND_API int _object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties ZEND_FILE_LINE_DC) /* {{{ */
{
     //檢查類是否可以例項化
    if (UNEXPECTED(class_type->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS))) {
        if (class_type->ce_flags & ZEND_ACC_INTERFACE) {
            //介面不能例項化
            zend_throw_error(NULL, "Cannot instantiate interface %s", ZSTR_VAL(class_type->name));
        } else if (class_type->ce_flags & ZEND_ACC_TRAIT) {
            //為實現多繼承定義的 trait開頭的,不能例項化
            zend_throw_error(NULL, "Cannot instantiate trait %s", ZSTR_VAL(class_type->name));
        } else {
            //抽象類不能例項化
            zend_throw_error(NULL, "Cannot instantiate abstract class %s", ZSTR_VAL(class_type->name));
        }
        ZVAL_NULL(arg);
        Z_OBJ_P(arg) = NULL;
        return FAILURE;
    }

    if (UNEXPECTED(!(class_type->ce_flags & ZEND_ACC_CONSTANTS_UPDATED))) {
        if (UNEXPECTED(zend_update_class_constants(class_type) != SUCCESS)) {
            ZVAL_NULL(arg);
            Z_OBJ_P(arg) = NULL;
            return FAILURE;
        }
    }

    //使用者自定義的類create_object都是NULL
    //只有PHP幾個內部的類有這個值,比如exception、error等
    if (class_type->create_object == NULL) {
        //分配一個物件     分配物件結構:zend_object ******
        ZVAL_OBJ(arg, zend_objects_new(class_type));
        if (properties) {
             //初始化成員屬性****
            object_properties_init_ex(Z_OBJ_P(arg), properties);
        } else {
            object_properties_init(Z_OBJ_P(arg), class_type);
        }
    } else {
        //呼叫自定義的建立object的鉤子函式
        ZVAL_OBJ(arg, class_type->create_object(class_type));
    }
    return SUCCESS;
}

zend_objects_new:

//注意:此操作並沒有將屬性複製到zend_object中:由object_properties_init()完成
ZEND_API zend_object *zend_objects_new(zend_class_entry *ce)
{
    //properties_table 是一個變長陣列 存放非靜態屬性的值, 分配zend_object時需要加上非靜態屬性所佔用的記憶體大小
    //根據普通非靜態屬性個數確定,如果沒有定義__get()、__set()等魔術方法則佔用記憶體就是: 屬性數*sizeof(zval),
    //如果定義了這些魔術方法那麼會多分配一個zval的空間 *******************
    //分配zend_object

    zend_object *object = emalloc(sizeof(zend_object) + zend_object_properties_size(ce));

    zend_object_std_init(object, ce);
    //設定物件的操作handler為std_object_handlers
    object->handlers = &std_object_handlers;
    return object;
}

zend_object_std_init:

ZEND_API void zend_object_std_init(zend_object *object, zend_class_entry *ce)
{
    zval *p, *end;

    GC_REFCOUNT(object) = 1;
    GC_TYPE_INFO(object) = IS_OBJECT;
    object->ce = ce;
    object->properties = NULL;
    //將object編號並插入EG(objects_store).object_buckets陣列
    zend_objects_store_put(object);  //next
    p = object->properties_table;
    if (EXPECTED(ce->default_properties_count != 0)) {
        end = p + ce->default_properties_count;
        //值指向,寫時複製;
        do {
            ZVAL_UNDEF(p);
            p++;
        } while (p != end);
    }
    if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
        GC_FLAGS(object) |= IS_OBJ_USE_GUARDS;
        ZVAL_UNDEF(p);
    }
}

zend_objects_store_put

3) get_constructor
zend_vm_stack_push_call_frame

ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER(本頁最簡單的程式碼中會跳過這個handler

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zend_execute_data *call = EX(call);
    zend_function *fbc = call->func;
    zend_object *object;
    zval *ret;

    SAVE_OPLINE();
    EX(call) = call->prev_execute_data;
    //deprecated棄用, 抽象的
    if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_DEPRECATED)) != 0)) {
        if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_ABSTRACT) != 0)) {
            //不能呼叫抽象方法
            zend_throw_error(NULL, "Cannot call abstract method %s::%s()", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
            HANDLE_EXCEPTION();
        }
        if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) {
            //棄用的方法
            zend_error(E_DEPRECATED, "Function %s%s%s() is deprecated",
                fbc->common.scope ? ZSTR_VAL(fbc->common.scope->name) : "",
                fbc->common.scope ? "::" : "",
                ZSTR_VAL(fbc->common.function_name));
            if (UNEXPECTED(EG(exception) != NULL)) {
                HANDLE_EXCEPTION();
            }
        }
    }

    LOAD_OPLINE();  //LOAD_OPLINE() opline = EX(opline)
    //使用者定義的類
    if (EXPECTED(fbc->type == ZEND_USER_FUNCTION)) {
        ret = NULL;
        if (0) {
            ret = EX_VAR(opline->result.var);
            ZVAL_NULL(ret);
        }
        //把當前的execute_data儲存一下
        call->prev_execute_data = execute_data;
        //初始化當前方法的execute_data
        i_init_func_execute_data(call, &fbc->op_array, ret);

        if (EXPECTED(zend_execute_ex == execute_ex)) {
            ZEND_VM_ENTER();
        } else {
//#define ZEND_ADD_CALL_FLAG_EX(call_info, flag) do { \
        //  call_info |= ((flag) << ZEND_CALL_INFO_SHIFT); \
        //} while (0)

//#define ZEND_ADD_CALL_FLAG(call, flag) do { \
            //ZEND_ADD_CALL_FLAG_EX(Z_TYPE_INFO((call)->This), flag); \
    //} while (0)
            ZEND_ADD_CALL_FLAG(call, ZEND_CALL_TOP);
            zend_execute_ex(call); //(*zend_execute_ex)(zend_execute_data *execute_data);
        }
    } else if (EXPECTED(fbc->type < ZEND_USER_FUNCTION)) {// 非使用者定義的
        zval retval;

        call->prev_execute_data = execute_data;
        EG(current_execute_data) = call;

        /* function has typed arguments */
        //#define ZEND_ACC_HAS_TYPE_HINTS           0x10000000   hints提示
        if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_HAS_TYPE_HINTS)
                //internal內部  verify驗證  所有引數的型別
          && UNEXPECTED(!zend_verify_internal_arg_types(fbc, call))) {
            if (0) {
                ZVAL_UNDEF(EX_VAR(opline->result.var));
            }
            goto fcall_end;
        }

        ret = 0 ? EX_VAR(opline->result.var) : &retval;
        ZVAL_NULL(ret);

        if (!zend_execute_internal) {
            /* saves one function call if zend_execute_internal is not used */
            //如果沒有使用zend_execute_internal,則儲存一個函式呼叫
            fbc->internal_function.handler(call, ret);
        } else {
            zend_execute_internal(call, ret);
        }

#if ZEND_DEBUG
        if (!EG(exception) && call->func) {
            ZEND_ASSERT(!(call->func->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) ||
                zend_verify_internal_return_type(call->func, ret));
            ZEND_ASSERT((call->func->common.fn_flags & ZEND_ACC_RETURN_REFERENCE)
                ? Z_ISREF_P(ret) : !Z_ISREF_P(ret));
        }
#endif

        EG(current_execute_data) = call->prev_execute_data;
        zend_vm_stack_free_args(call);

        if (!0) {
            //銷燬
            zval_ptr_dtor(ret);
        }

    } else { /* ZEND_OVERLOADED_FUNCTION */  //過載
        zval retval;

        ret = 0 ? EX_VAR(opline->result.var) : &retval;

        call->prev_execute_data = execute_data;

        if (UNEXPECTED(!zend_do_fcall_overloaded(fbc, call, ret))) {
            HANDLE_EXCEPTION();
        }

        if (!0) {
            zval_ptr_dtor(ret);
        }
    }

fcall_end:
    if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS)) {
        object = Z_OBJ(call->This);
#if 0
        if (UNEXPECTED(EG(exception) != NULL) && (opline->op1.num & ZEND_CALL_CTOR)) {
#else
        if (UNEXPECTED(EG(exception) != NULL) && (ZEND_CALL_INFO(call) & ZEND_CALL_CTOR)) {
#endif
            GC_REFCOUNT(object)--;
            //有異常
            zend_object_store_ctor_failed(object);
        }
        OBJ_RELEASE(object);
    }

    zend_vm_stack_free_call_frame(call);
    if (UNEXPECTED(EG(exception) != NULL)) {
        //有異常
        zend_throw_exception_internal(NULL);
        if (0) {
            zval_ptr_dtor(EX_VAR(opline->result.var));
        }
        HANDLE_EXCEPTION();
    }

    ZEND_VM_SET_OPCODE(opline + 1);
    ZEND_VM_CONTINUE();
}

i_init_func_execute_data

zend_do_fcall_overloaded

static zend_never_inline int zend_do_fcall_overloaded(zend_function *fbc, zend_execute_data *call, zval *ret) /* {{{ */
{
    zend_object *object;

    /* Not sure what should be done here if it's a static method */
    //如果它是一個靜態方法,不知道這裡應該做什麼
    if (UNEXPECTED(Z_TYPE(call->This) != IS_OBJECT)) {
        //不是物件
        zend_vm_stack_free_args(call);
        if (fbc->type == ZEND_OVERLOADED_FUNCTION_TEMPORARY) {
            zend_string_release(fbc->common.function_name);
        }
        efree(fbc);
        zend_vm_stack_free_call_frame(call);

        zend_throw_error(NULL, "Cannot call overloaded function for non-object");
        return 0;
    }

    object = Z_OBJ(call->This);

    ZVAL_NULL(ret);

    EG(current_execute_data) = call;
    //呼叫方法, 引數:方法,物件,引數,返回值
    object->handlers->call_method(fbc->common.function_name, object, call, ret);
    EG(current_execute_data) = call->prev_execute_data;

    zend_vm_stack_free_args(call);

    if (fbc->type == ZEND_OVERLOADED_FUNCTION_TEMPORARY) {
        zend_string_release(fbc->common.function_name);
    }
    efree(fbc);

    return 1;
}

ZEND_ASSIGN_SPEC_CV_VAR_RETVAL_UNUSED_HANDLER

static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_VAR_RETVAL_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    USE_OPLINE
    zend_free_op free_op2;
    zval *value;
    zval *variable_ptr;

    SAVE_OPLINE(); //EX(opline) = opline
    //zval *ret = EX_VAR(var);
    //*should_free = ret;
    //return ret;
    value = _get_zval_ptr_var(opline->op2.var, execute_data, &free_op2);
    //EX_VAR(var);第二個引數,| EX_VAR(n) END_CALL_VAR(execute_data, n)
    variable_ptr = _get_zval_ptr_cv_undef_BP_VAR_W(execute_data, opline->op1.var);
    //不走
    if (IS_CV == IS_VAR && UNEXPECTED(Z_ISERROR_P(variable_ptr))) {
        zval_ptr_dtor_nogc(free_op2);
        if (UNEXPECTED(0)) {
            ZVAL_NULL(EX_VAR(opline->result.var));
        }
    } else {
        //賦值
        value = zend_assign_to_variable(variable_ptr, value, IS_VAR);
        if (UNEXPECTED(0)) {
            ZVAL_COPY(EX_VAR(opline->result.var), value);
        }
        //zend_assign_to_variable()總是處理op2,從不釋放它
        /* zend_assign_to_variable() always takes care of op2, never free it! */
    }

    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

zend_assign_to_variable

ZEND_RETURN_SPEC_CONST_HANDLER

  • step1: 首先根據類名去EG(class_table)中找到具體的類,即zend_class_entry
  • step2: 分配zend_object結構,一起分配的還有普通非靜態屬性值的記憶體
  • step3: 初始化物件的非靜態屬性,將屬性值從zend_class_entry淺複製到物件中
  • step4: 查詢當前類是否定義了建構函式,如果沒有定義則跳過執行建構函式的opcode,否則為呼叫建構函式的執行進行一些準備工作(分配zend_execute_data)
  • step5: 例項化完成,返回新例項化的物件(如果返回的物件沒有變數使用則直接釋放掉了)
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章