簡(jiǎn)單對(duì)比一下PHP 7 和 PHP 5 中的對(duì)象(轉(zhuǎn)載)

本篇文章帶大家了解一下PHP 7 和 PHP 5 中對(duì)象挪略,并比較一下磨确,看看它們之間的差異!

image

一桶蝎、 class 介紹

PHP 中的 class驻仅、interface、trait 在底層均以 zend_class_entry 結(jié)構(gòu)體實(shí)現(xiàn)

struct _zend_class_entry {
    char type;
    const char *name;
    zend_uint name_length;
    struct _zend_class_entry *parent;
    int refcount;
    zend_uint ce_flags;

    HashTable function_table;
    HashTable properties_info;
    zval **default_properties_table;
    zval **default_static_members_table;
    zval **static_members_table;
    HashTable constants_table;
    int default_properties_count;
    int default_static_members_count;

    union _zend_function *constructor;
    union _zend_function *destructor;
    union _zend_function *clone;
    union _zend_function *__get;
    union _zend_function *__set;
    union _zend_function *__unset;
    union _zend_function *__isset;
    union _zend_function *__call;
    union _zend_function *__callstatic;
    union _zend_function *__tostring;
    union _zend_function *serialize_func;
    union _zend_function *unserialize_func;

    zend_class_iterator_funcs iterator_funcs;

    /* handlers */
    zend_object_value (*create_object)(zend_class_entry *class_type TSRMLS_DC);
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref TSRMLS_DC);
    int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type TSRMLS_DC); /* a class implements this interface */
    union _zend_function *(*get_static_method)(zend_class_entry *ce, char* method, int method_len TSRMLS_DC);

    /* serializer callbacks */
    int (*serialize)(zval *object, unsigned char **buffer, zend_uint *buf_len, zend_serialize_data *data TSRMLS_DC);
    int (*unserialize)(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC);

    zend_class_entry **interfaces;
    zend_uint num_interfaces;

    zend_class_entry **traits;
    zend_uint num_traits;
    zend_trait_alias **trait_aliases;
    zend_trait_precedence **trait_precedences;

    union {
        struct {
            const char *filename;
            zend_uint line_start;
            zend_uint line_end;
            const char *doc_comment;
            zend_uint doc_comment_len;
        } user;
        struct {
            const struct _zend_function_entry *builtin_functions;
            struct _zend_module_entry *module;
        } internal;
    } info;
};

zend_class_entry 結(jié)構(gòu)體中包含大量的指針以及 hashtable登渣,這就導(dǎo)致結(jié)構(gòu)體本身會(huì)占用不小的內(nèi)存空間噪服。另外,結(jié)構(gòu)體中的指針還需要單獨(dú)分配相應(yīng)的內(nèi)存空間胜茧,這又會(huì)消耗一部分內(nèi)存空間粘优。

⒈ 開發(fā)者自定義的 class 與 PHP 內(nèi)部定義的 class 的比較

所謂開發(fā)者自定義的 class 即使用 PHP 語(yǔ)言定義的 class,而 PHP 內(nèi)部定義的 class 是指 PHP 源代碼中定義的 class 或 PHP 擴(kuò)展中定義的 class。二者最本質(zhì)的區(qū)別在于生命周期不同:

  • 以 php-fpm 為例敬飒,當(dāng)請(qǐng)求到來時(shí)邪铲,PHP 會(huì)解析開發(fā)者定義的 class 并為其分配相應(yīng)的內(nèi)存空間。其后在處理請(qǐng)求的過程中无拗,PHP 會(huì)對(duì)這些 class 進(jìn)行相應(yīng)的調(diào)用带到,最后在處理完請(qǐng)求之后銷毀這些 class,釋放之前為其分配的內(nèi)存空間英染。

為了節(jié)約內(nèi)存空間揽惹,不要在代碼中定義一些實(shí)際并不使用的 class∷目担可以使用 autoload 來屏蔽這些實(shí)際并不使用的 class搪搏,因?yàn)?autoload 只有在一個(gè) class 被用到時(shí)才加載和解析,但這樣就會(huì)把 class 的解析和加載過程由代碼的編譯階段延后到代碼的執(zhí)行階段闪金,影響性能

另外需要注意的是疯溺,即使開啟了 OPCache 擴(kuò)展,開發(fā)者自定義的 class 還是會(huì)隨著請(qǐng)求的到來而解析和加載哎垦,隨著請(qǐng)求的完成而銷毀囱嫩,OPCache 只是提高了這兩個(gè)階段的速度

  • PHP 內(nèi)部定義的 class 則不同。仍然以 php-fpm 為例漏设,當(dāng)一個(gè) php-fpm 進(jìn)程啟動(dòng)時(shí)墨闲,PHP 會(huì)為這些 class 一次性永久分配內(nèi)存空間,直到此 php-fpm 進(jìn)程消亡(為避免內(nèi)存泄漏郑口,php-fpm 會(huì)在處理完一定數(shù)量的請(qǐng)求之后銷毀然后重啟)
if (EG(full_tables_cleanup)) {
    zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
    zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC);
} else {
    zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function TSRMLS_CC);
    zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC);
}

static int clean_non_persistent_class(zend_class_entry **ce TSRMLS_DC)
{
    return ((*ce)->type == ZEND_INTERNAL_CLASS) ? ZEND_HASH_APPLY_STOP : ZEND_HASH_APPLY_REMOVE;
}

由以上代碼可以看出鸳碧,在請(qǐng)求結(jié)束時(shí),PHP 內(nèi)部定義的 class 并不會(huì)被銷毀犬性。另外瞻离,由于 PHP 擴(kuò)展中定義的 class 也屬于 PHP 內(nèi)部定義的 class 的范疇,所以乒裆,從節(jié)省內(nèi)存空間的角度出發(fā)琐脏,不要開啟一些自己并不使用的擴(kuò)展。因?yàn)楦淄茫绻麛U(kuò)展一旦開啟,擴(kuò)展中定義的 class 就會(huì)在 php-fpm 進(jìn)程啟動(dòng)時(shí)被解析和加載吹艇。

很多時(shí)候惰蜜,為了處理方便,我們會(huì)通過繼承 \Exception 來自定義 exception受神。但由于 zend_class_entry 結(jié)構(gòu)體非常龐大抛猖,這就導(dǎo)致在提高便利的同時(shí)耗費(fèi)了大量的內(nèi)存

⒉ class 綁定

class 綁定指的是 class 數(shù)據(jù)的準(zhǔn)備過程

對(duì)于 PHP 內(nèi)部定義的 class,綁定過程在 class 注冊(cè)時(shí)就已經(jīng)完成。此過程發(fā)生在 PHP 腳本運(yùn)行之前财著,并且在整個(gè) php-fpm 進(jìn)程的生命周期中只發(fā)生一次联四。

對(duì)于既沒有繼承 parent class,也沒有實(shí)現(xiàn) interface撑教,也沒有使用 trait 的 class朝墩,綁定過程發(fā)生在 PHP 代碼的編輯階段,并且不會(huì)消耗太多資源伟姐。此種 class 的綁定通常只需要將 class 注冊(cè)到 class_table 中收苏,并檢查 class 是否包含了抽象方法但沒有被申明為 abstract 類型。

void zend_do_early_binding(TSRMLS_D) /* {{{ */
{
    zend_op *opline = &CG(active_op_array)->opcodes[CG(active_op_array)->last-1];
    HashTable *table;

    while (opline->opcode == ZEND_TICKS && opline > CG(active_op_array)->opcodes) {
        opline--;
    }

    switch (opline->opcode) {
        case ZEND_DECLARE_FUNCTION:
            if (do_bind_function(CG(active_op_array), opline, CG(function_table), 1) == FAILURE) {
                return;
            }
            table = CG(function_table);
            break;
        case ZEND_DECLARE_CLASS:
            if (do_bind_class(CG(active_op_array), opline, CG(class_table), 1 TSRMLS_CC) == NULL) {
                return;
            }
            table = CG(class_table);
            break;
        case ZEND_DECLARE_INHERITED_CLASS:
            {
                /*... ...*/
            }
        case ZEND_VERIFY_ABSTRACT_CLASS:
        case ZEND_ADD_INTERFACE:
        case ZEND_ADD_TRAIT:
        case ZEND_BIND_TRAITS:
            /* We currently don't early-bind classes that implement interfaces */
            /* Classes with traits are handled exactly the same, no early-bind here */
            return;
        default:
            zend_error(E_COMPILE_ERROR, "Invalid binding type");
            return;
    }

/*... ...*/
}

void zend_verify_abstract_class(zend_class_entry *ce TSRMLS_DC)
{
    zend_abstract_info ai;

    if ((ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) && !(ce->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) {
        memset(&ai, 0, sizeof(ai));

        zend_hash_apply_with_argument(&ce->function_table, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC);

        if (ai.cnt) {
            zend_error(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")",
                ce->name, ai.cnt,
                ai.cnt > 1 ? "s" : "",
                DISPLAY_ABSTRACT_FN(0),
                DISPLAY_ABSTRACT_FN(1),
                DISPLAY_ABSTRACT_FN(2)
                );
        }
    }
}

對(duì)于實(shí)現(xiàn)了 interface 的 class 的綁定過程非常復(fù)雜愤兵,大致流程如下:

  • 檢查 interface 是否已經(jīng)實(shí)現(xiàn)
  • 檢查實(shí)現(xiàn)該 interface 的確實(shí)是一個(gè) class鹿霸,而不是 interface 自身(class、interface秆乳、trait 的底層數(shù)據(jù)結(jié)構(gòu)都是 zend_class_entry)
  • 復(fù)制常量懦鼠,并檢查可能存在的沖突
  • 復(fù)制方法,并檢查可能存在的沖突屹堰,除此之外還需要檢查訪問控制
  • 將 interface 加入到 zend_class_entry 的 **interfaces

需要注意的是肛冶,所謂的復(fù)制只是將常量、屬性双藕、方法的引用計(jì)數(shù)加 1

ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry *iface TSRMLS_DC)
{
    /* ... ... */

    } else {
        if (ce->num_interfaces >= current_iface_num) { /* resize the vector if needed */
            if (ce->type == ZEND_INTERNAL_CLASS) {
                /*對(duì)于內(nèi)部定義的 class淑趾,使用 realloc 分配內(nèi)存,所分配的內(nèi)存在進(jìn)程的生命周期中永久有效*/
                ce->interfaces = (zend_class_entry **) realloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num));
            } else {
                /*對(duì)于開發(fā)者定義的 class忧陪,使用 erealloc 分配內(nèi)存扣泊,所分配的內(nèi)存只在請(qǐng)求的生命周期中有效*/
                ce->interfaces = (zend_class_entry **) erealloc(ce->interfaces, sizeof(zend_class_entry *) * (++current_iface_num));
            }
        }
        ce->interfaces[ce->num_interfaces++] = iface; /* Add the interface to the class */

        /* Copy every constants from the interface constants table to the current class constants table */
        zend_hash_merge_ex(&ce->constants_table, &iface->constants_table, (copy_ctor_func_t) zval_add_ref, sizeof(zval *), (merge_checker_func_t) do_inherit_constant_check, iface);
        /* Copy every methods from the interface methods table to the current class methods table */
        zend_hash_merge_ex(&ce->function_table, &iface->function_table, (copy_ctor_func_t) do_inherit_method, sizeof(zend_function), (merge_checker_func_t) do_inherit_method_check, ce);

        do_implement_interface(ce, iface TSRMLS_CC);
        zend_do_inherit_interfaces(ce, iface TSRMLS_CC);
    }
}

對(duì)于常量的復(fù)制,zval_add_ref 用于將常量的引用計(jì)數(shù)加1嘶摊;而對(duì)于方法的復(fù)制延蟹,do_inherit_method 除了將相應(yīng)方法的引用計(jì)數(shù)加 1 之外,還將方法中定義的靜態(tài)變量的引用計(jì)數(shù)加 1叶堆。

static void do_inherit_method(zend_function *function)
{
    function_add_ref(function);
}

ZEND_API void function_add_ref(zend_function *function)
{
    if (function->type == ZEND_USER_FUNCTION) {
        zend_op_array *op_array = &function->op_array;

        (*op_array->refcount)++;
        if (op_array->static_variables) {
            HashTable *static_variables = op_array->static_variables;
            zval *tmp_zval;

            ALLOC_HASHTABLE(op_array->static_variables);
            zend_hash_init(op_array->static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0);
            zend_hash_copy(op_array->static_variables, static_variables, (copy_ctor_func_t) zval_add_ref, (void *) &tmp_zval, sizeof(zval *));
        }
        op_array->run_time_cache = NULL;
    }
}

對(duì)于實(shí)現(xiàn)了 interface 的 class 的綁定阱飘,由于要進(jìn)行多次的循環(huán)遍歷以及檢查,通常非常消耗 CPU 資源虱颗,但卻節(jié)省了內(nèi)存空間沥匈。

現(xiàn)階段,PHP 將 interface 的綁定推遲到了代碼執(zhí)行階段進(jìn)行忘渔,以為這每次請(qǐng)求都會(huì)進(jìn)行這些操作

對(duì)于 class 繼承的綁定高帖,過程與 interface 的綁定類似,但更為復(fù)雜畦粮。另外有一個(gè)值得注意的地方散址,如果 class 在綁定時(shí)已經(jīng)解析到了父類乖阵,則綁定發(fā)生在代碼編譯階段;否則發(fā)生在代碼執(zhí)行階段预麸。

// A 在 B 之前申明瞪浸,B 的綁定發(fā)生在編譯階段
class A { }
class B extends A { }

// A 在 B 之后申明,綁定 B 時(shí)編譯器無(wú)法知道 A 情況吏祸,此時(shí) B 的綁定只能延后到代碼執(zhí)行時(shí)
class B extends A { }
class A { }

// 這種情況會(huì)報(bào)錯(cuò):Class B doesn't exist
// 在代碼執(zhí)行階段綁定 C对蒲,需要解析 B,但此時(shí) B 有繼承了 A犁罩,而 A 此時(shí)還是未知狀態(tài)
class C extends B { }
class B extends A { }
class A { }

如果使用 autoload齐蔽,并且采用一個(gè) class 對(duì)應(yīng)一個(gè)文件的模式,則所有 class 的綁定都只會(huì)發(fā)生在代碼執(zhí)行階段

二床估、PHP 5 中的 object

⒈ object 中的方法

方法與函數(shù)的底層數(shù)據(jù)結(jié)構(gòu)均為 zend_function含滴。PHP 編譯器在編譯時(shí)將方法編譯并添加到 zend_class_entry 的 function_table 屬性中。所以丐巫,在 PHP 代碼運(yùn)行時(shí)谈况,方法已經(jīng)編譯完成,PHP 要做的只是通過指針找到方法并執(zhí)行递胧。

typedef union _zend_function {
    zend_uchar type;

    struct {
        zend_uchar type;
        const char *function_name;
        zend_class_entry *scope;
        zend_uint fn_flags;
        union _zend_function *prototype;
        zend_uint num_args;
        zend_uint required_num_args;
        zend_arg_info *arg_info;
    } common;

    zend_op_array op_array;
    zend_internal_function internal_function;
} zend_function;

當(dāng) object 嘗試調(diào)用方法時(shí)碑韵,首先會(huì)在其對(duì)應(yīng)的 class 的 function_table 中查找該方法,同時(shí)還會(huì)檢查方法的訪問控制缎脾。如果方法不存在或方法的訪問控制不符合要求熄诡,object 會(huì)嘗試調(diào)用莫屬方法 __call饭尝。

static inline union _zend_function *zend_get_user_call_function(zend_class_entry *ce, const char *method_name, int method_len) 
{
    zend_internal_function *call_user_call = emalloc(sizeof(zend_internal_function));
    call_user_call->type = ZEND_INTERNAL_FUNCTION;
    call_user_call->module = (ce->type == ZEND_INTERNAL_CLASS) ? ce->info.internal.module : NULL;
    call_user_call->handler = zend_std_call_user_call;
    call_user_call->arg_info = NULL;
    call_user_call->num_args = 0;
    call_user_call->scope = ce;
    call_user_call->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
    call_user_call->function_name = estrndup(method_name, method_len);

    return (union _zend_function *)call_user_call;
}

static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const zend_literal *key TSRMLS_DC)
{
    zend_function *fbc;
    zval *object = *object_ptr;
    zend_object *zobj = Z_OBJ_P(object);
    ulong hash_value;
    char *lc_method_name;
    ALLOCA_FLAG(use_heap)

    if (EXPECTED(key != NULL)) {
        lc_method_name = Z_STRVAL(key->constant);
        hash_value = key->hash_value;
    } else {
        lc_method_name = do_alloca(method_len+1, use_heap);
        /* Create a zend_copy_str_tolower(dest, src, src_length); */
        zend_str_tolower_copy(lc_method_name, method_name, method_len);
        hash_value = zend_hash_func(lc_method_name, method_len+1);
    }

    if (UNEXPECTED(zend_hash_quick_find(&zobj->ce->function_table, lc_method_name, method_len+1, hash_value, (void **)&fbc) == FAILURE)) {
        if (UNEXPECTED(!key)) {
            free_alloca(lc_method_name, use_heap);
        }
        if (zobj->ce->__call) {
            return zend_get_user_call_function(zobj->ce, method_name, method_len);
        } else {
            return NULL;
        }
    }

    /* Check access level */
    if (fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) {
        zend_function *updated_fbc;

        /* Ensure that if we're calling a private function, we're allowed to do so.
        * If we're not and __call() handler exists, invoke it, otherwise error out.
        */
        updated_fbc = zend_check_private_int(fbc, Z_OBJ_HANDLER_P(object, get_class_entry)(object TSRMLS_CC), lc_method_name, method_len, hash_value TSRMLS_CC);
        if (EXPECTED(updated_fbc != NULL)) {
            fbc = updated_fbc;
        } else {
            if (zobj->ce->__call) {
                fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
            } else {
                zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
            }
        }
    } else {
        /* Ensure that we haven't overridden a private function and end up calling
        * the overriding public function...
        */
        if (EG(scope) &&
            is_derived_class(fbc->common.scope, EG(scope)) &&
            fbc->op_array.fn_flags & ZEND_ACC_CHANGED) {
            zend_function *priv_fbc;

            if (zend_hash_quick_find(&EG(scope)->function_table, lc_method_name, method_len+1, hash_value, (void **) &priv_fbc)==SUCCESS
                && priv_fbc->common.fn_flags & ZEND_ACC_PRIVATE
                && priv_fbc->common.scope == EG(scope)) {
                fbc = priv_fbc;
            }
        }
        if ((fbc->common.fn_flags & ZEND_ACC_PROTECTED)) {
            /* Ensure that if we're calling a protected function, we're allowed to do so.
            * If we're not and __call() handler exists, invoke it, otherwise error out.
            */
            if (UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc), EG(scope)))) {
                if (zobj->ce->__call) {
                    fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
                } else {
                    zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
                }
            }
        }
    }

    if (UNEXPECTED(!key)) {
        free_alloca(lc_method_name, use_heap);
    }
    return fbc;
}

這里需要指出的是:

  • 由于 PHP 對(duì)大小寫不敏感,所以所有的方法名稱都會(huì)被轉(zhuǎn)為小寫(zend_str_tolower_copy())
  • 為了避免不必要的資源消耗,PHP 5.4 開始引入了 zend_literal 結(jié)構(gòu)體纳鼎,即參數(shù) key
typedef struct _zend_literal {
    zval       constant;
    zend_ulong hash_value;
    zend_uint  cache_slot;
} zend_literal;

其中驳棱,constant 記錄了轉(zhuǎn)為小寫后的字符串生兆,hash_value 則是預(yù)先計(jì)算好的 hash廊佩。這樣就避免了 object 每次調(diào)用方法都要將方法名稱轉(zhuǎn)為小寫并計(jì)算 hash 值。

class Foo { public function BAR() { } }
$a = new Foo;
$b = 'bar';

$a->bar(); /* good */
$a->$b(); /* bad */

在上例中贺拣,在代碼編譯階段蓖谢,方法 BAR 被轉(zhuǎn)換成 bar 并添加到 zend_class_entry 的 function_table 中。當(dāng)發(fā)生方法調(diào)用時(shí):

  • 第一種情形譬涡,在代碼編譯階段闪幽,方法名稱 bar 確定為字符串常量,編譯器可以預(yù)先計(jì)算好其對(duì)應(yīng)的 zend_literal 結(jié)構(gòu)涡匀,即 key 參數(shù)沟使。這樣,代碼在執(zhí)行時(shí)相對(duì)會(huì)更快渊跋。
  • 第二種情形腊嗡,由于在編譯階段編譯器對(duì) $b 一無(wú)所知,這就需要在代碼執(zhí)行階段現(xiàn)將方法名稱轉(zhuǎn)為小寫拾酝,然后計(jì)算 hash 值燕少。

⒉ object 中的屬性

當(dāng)對(duì)一個(gè) class 進(jìn)行實(shí)例化時(shí),object 中的屬性只是對(duì) class 中屬性的引用蒿囤。這樣客们,object 的創(chuàng)建操作就會(huì)相對(duì)輕量化,并且會(huì)節(jié)省一部分內(nèi)存空間材诽。

1.png

如果要對(duì) object 中的屬性進(jìn)行修改底挫,zend 引擎會(huì)單獨(dú)創(chuàng)建一個(gè) zval 結(jié)構(gòu),只對(duì)當(dāng)前 object 的當(dāng)前屬性產(chǎn)生影響脸侥。

2.png

class 的實(shí)例化對(duì)應(yīng)的會(huì)在底層創(chuàng)建一個(gè) zend_obejct 數(shù)據(jù)結(jié)構(gòu)建邓,新創(chuàng)建的 object 會(huì)注冊(cè)到 zend_objects_store 中。zend_objects_store 是一個(gè)全局的 object 注冊(cè)表睁枕,同一個(gè)對(duì)象在該注冊(cè)表中只能注冊(cè)一次官边。

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;

typedef struct _zend_objects_store {/*本質(zhì)上是一個(gè)動(dòng)態(tài) object_bucket 數(shù)組*/
    zend_object_store_bucket *object_buckets;
    zend_uint top; /*下一個(gè)可用的 handle,handle 取值從 1 開始外遇。對(duì)應(yīng)的在 *object_buckets 中的 index 為 handle - 1*/
    zend_uint size; /*當(dāng)前分配的 *object_buckets 的最大長(zhǎng)度*/
    int free_list_head; /*當(dāng) *object_bucket 中的 bucket 被銷毀后注簿,該 bucket 在 *object_buckets 中的 index 會(huì)被有序加入 free_list 鏈表。free_list_head 即為該鏈表中的第一個(gè)值*/
} zend_objects_store;

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid; /*值為 1 表示當(dāng)前 bucket 被使用跳仿,此時(shí) store_bucket 中的 store_object 被使用诡渴;值為 0 表示當(dāng)前 bucket 并沒有存儲(chǔ)有效的 object,此時(shí) store_bucket 中的 free_list 被使用*/
    zend_uchar apply_count;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next; /*第一個(gè)未被使用的 bucket 的 index 永遠(yuǎn)存儲(chǔ)在 zend_object_store 的 free_list_head 中菲语,所以 next 只需要記錄當(dāng)前 bucket 之后第一個(gè)未被使用的 bucket 的 index*/
        } free_list;
    } bucket;
} zend_object_store_bucket;

ZEND_API zend_object_value zend_objects_new(zend_object **object, zend_class_entry *class_type TSRMLS_DC)
{
    zend_object_value retval;

    *object = emalloc(sizeof(zend_object));
    (*object)->ce = class_type;
    (*object)->properties = NULL;
    (*object)->properties_table = NULL;
    (*object)->guards = NULL;
    retval.handle = zend_objects_store_put(*object, (zend_objects_store_dtor_t) zend_objects_destroy_object, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC);
    retval.handlers = &std_object_handlers;
    return retval;
}

將 object 注冊(cè)到 zend_objects_store 中以后妄辩,將會(huì)為 object 創(chuàng)建屬性(對(duì)相應(yīng) class 屬性的引用)

ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type) 
{
    int i;

    if (class_type->default_properties_count) {
        object->properties_table = emalloc(sizeof(zval*) * class_type->default_properties_count);
        for (i = 0; i < class_type->default_properties_count; i++) {
            object->properties_table[i] = class_type->default_properties_table[i];
            if (class_type->default_properties_table[i]) {
#if ZTS
                ALLOC_ZVAL( object->properties_table[i]);
                MAKE_COPY_ZVAL(&class_type->default_properties_table[i], object->properties_table[i]);
#else
                Z_ADDREF_P(object->properties_table[i]);
#endif
            }
        }
        object->properties = NULL;
    }
}

需要指出的是,在創(chuàng)建屬性時(shí)谨究,如果是非線程安全模式的 PHP恩袱,僅僅是增加相應(yīng)屬性的引用計(jì)數(shù);但如果是線程安全模式的 PHP胶哲,則需要對(duì)屬性進(jìn)行深度復(fù)制畔塔,將 class 的屬性全部復(fù)制到 object 中的 properties_table 中。

這也說明鸯屿,線程安全的 PHP 比非線程安全的 PHP 運(yùn)行慢澈吨,并且更耗費(fèi)內(nèi)存

每個(gè)屬性在底層都對(duì)應(yīng)一個(gè) zend_property_info 結(jié)構(gòu):

typedef struct _zend_property_info {
    zend_uint flags;
    const char *name;
    int name_length;
    ulong h;
    int offset;
    const char *doc_comment;
    int doc_comment_len;
    zend_class_entry *ce;
} zend_property_info;

class 中聲明的每個(gè)屬性,在 zend_class_entry 中的 properties_table 中都有一個(gè)zend_property_info 與之相對(duì)應(yīng)寄摆。properties_table 可以幫助我們快速確定一個(gè) object 所訪問的屬性是否存在:

  • 如果屬性不存在谅辣,并且我們嘗試向 object 寫入該屬性:如果 class 定義了 __set 方法,則使用 __set 方法寫入該屬性婶恼;否則會(huì)向 object 添加一個(gè)動(dòng)態(tài)屬性桑阶。但無(wú)論以何種方式寫入該屬性柏副,寫入的屬性都將添加到 object 的 properties_table 中。
  • 如果屬性存在蚣录,則需要檢查相應(yīng)的訪問控制割择;對(duì)于 protected 和 private 類型,則需要檢查當(dāng)前的作用域萎河。

在創(chuàng)建完 object 之后荔泳,只要我們不向 object 中寫入新的屬性或更新 object 對(duì)應(yīng)的 class 中的屬性的值,則 object 所占用的內(nèi)存空間不會(huì)發(fā)生變化虐杯。

屬性的存儲(chǔ)/訪問方式:
zend_class_entry->properties_info 中存儲(chǔ)的是一個(gè)個(gè)的 zend_property_info玛歌。而屬性的值實(shí)際以 zval 指針數(shù)組的方式存儲(chǔ)在 zend_class_entry->default_properties_table 中。object 中動(dòng)態(tài)添加的屬性只會(huì)以 property_name => property_value 的形式存儲(chǔ)在 zend_object->properties_table 中擎椰。而在創(chuàng)建 object 時(shí)支子,zend_class_entry->properties_table 中的值會(huì)被逐個(gè)傳遞給 zend_object->properties_table。
zend_literal->cache_slot 中存儲(chǔ)的 int 值為 run_time_cache 中的索引 index确憨。run_time_cache 為數(shù)組結(jié)構(gòu)译荞,index 對(duì)應(yīng)的 value 為訪問該屬性的 object 對(duì)應(yīng)的 zend_class_entry;index + 1 對(duì)應(yīng)的 value 為該屬性對(duì)應(yīng)的 zend_property_info 休弃。在訪問屬性時(shí)吞歼,如果 zend_literal->cache_slot 中的值不為空,則可以通過 zend_literal->cache_slot 快速檢索得到 zend_property_info 結(jié)構(gòu)塔猾;如果為空篙骡,則在檢索到 zend_property_info 的信息之后會(huì)初始化 zend_literal->cache_slot。

屬性名稱的存儲(chǔ)方式
private 屬性:"\0class_name\0property_name"
protected 屬性:"\0*\0property_name"
public 屬性:"property_name"

執(zhí)行以下代碼丈甸,看看輸出結(jié)果

class A {
    private $a = 'a';
    protected $b = 'b';
    public $c = 'c';
}

class B extends A {
    private $a = 'aa';
    protected $b = 'bb';
    public $c = 'cc';
}

class C extends B {
    private $a = 'aaa';
    protected $b = 'bbb';
    public $c = 'ccc';
}

var_dump(new C());

zend_object 中 guards 的作用
guards 的作用是對(duì) object 的重載提供遞歸保護(hù)糯俗。

class Foo {
    public function __set($name, $value) {
        $this->$name = $value;
    }
}

$foo = new Foo;
$foo->bar = 'baz';
var_dump($foo->bar);

以上代碼中,當(dāng)為 <math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><annotation encoding="application/x-tex">foo 動(dòng)態(tài)設(shè)置</annotation></semantics></math>foo動(dòng)態(tài)設(shè)置bar 屬性時(shí)會(huì)調(diào)用 __set 方法睦擂。但 $bar 屬性在 Foo 中并不存在得湘,按照常理,此時(shí)又會(huì)遞歸調(diào)用 __set 方法顿仇。為了避免這種遞歸調(diào)用淘正,PHP 會(huì)使用 zend_guard 來判斷當(dāng)前是否已經(jīng)處于重載方法的上下文中。

typedef struct _zend_guard {
    zend_bool in_get;
    zend_bool in_set;
    zend_bool in_unset;
    zend_bool in_isset;
    zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */
} zend_guard;

⒊ object 的引用傳遞

首先需要申明:object 并不是引用傳遞臼闻。之所以會(huì)出現(xiàn) object 是引用傳遞的假象鸿吆,原因在于我們傳遞給函數(shù)的參數(shù)中所存儲(chǔ)的只是 object 在 zend_objects_store 中的 ID(handle)。通過這個(gè) ID述呐,我們可以在 zend_objects_store 中查找并加載真正的 object惩淳,然后訪問并修改 object 中的屬性。

PHP 中乓搬,函數(shù)內(nèi)外是兩個(gè)不同的作用域思犁,對(duì)于同一變量代虾,在函數(shù)內(nèi)部對(duì)其修改不會(huì)影響到函數(shù)外部。但通過 object 的 ID(handle)訪問并修改 object 的屬性并不受此限制抒倚。

$a = 1;

function test($a) {
    $a = 3;
    echo $a; // 輸出 3
}

test($a);

echo $a; // 輸出 1
3.png

同一個(gè) object 在 zend_objects_store 中只存儲(chǔ)一次褐着。要向 zend_objects_store 中寫入新的對(duì)象,只能通過 new 關(guān)鍵字托呕、unserialize 函數(shù)、反射频敛、clone 四種方式项郊。

⒋ $this

$this 在使用時(shí)會(huì)自動(dòng)接管當(dāng)前對(duì)象,PHP 禁止對(duì) <math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><annotation encoding="application/x-tex">this 進(jìn)行賦值操作斟赚。任何對(duì)</annotation></semantics></math>this進(jìn)行賦值操作着降。任何對(duì)this 的賦值操作都會(huì)引起錯(cuò)誤

static zend_bool opline_is_fetch_this(const zend_op *opline TSRMLS_DC)
{
    if ((opline->opcode == ZEND_FETCH_W) && (opline->op1_type == IS_CONST)
        && (Z_TYPE(CONSTANT(opline->op1.constant)) == IS_STRING)
        && ((opline->extended_value & ZEND_FETCH_STATIC_MEMBER) != ZEND_FETCH_STATIC_MEMBER)
        && (Z_HASH_P(&CONSTANT(opline->op1.constant)) == THIS_HASHVAL)
        && (Z_STRLEN(CONSTANT(opline->op1.constant)) == (sizeof("this")-1))
        && !memcmp(Z_STRVAL(CONSTANT(opline->op1.constant)), "this", sizeof("this"))) {
        return 1;
    } else {
        return 0;
    }
}

/* ... ... */
if (opline_is_fetch_this(last_op TSRMLS_CC)) {
    zend_error(E_COMPILE_ERROR, "Cannot re-assign $this");
}
/* ... ... */

在 PHP 中進(jìn)行方法調(diào)用時(shí),對(duì)應(yīng)執(zhí)行的 OPCode 為 INIT_METHOD_CALL拗军。以 $a->foo() 為例任洞,在 INIT_METHOD_CALL 中,Zend 引擎知道是由 $a 發(fā)起的方法調(diào)用发侵,所以 Zend 引擎會(huì)把 $a 的值存入全局空間交掏。在實(shí)際執(zhí)行方法調(diào)用時(shí),對(duì)應(yīng)執(zhí)行的 OPCode 為 DO_FCALL刃鳄。在 DO_FCALL 中盅弛,Zend 引擎會(huì)將之前存入全局空間的 $a 賦值給 $this 的指針,即 EG(This):

if (fbc->type == ZEND_USER_FUNCTION || fbc->common.scope) {
    should_change_scope = 1;
    EX(current_this) = EG(This);
    EX(current_scope) = EG(scope);
    EX(current_called_scope) = EG(called_scope);
    EG(This) = EX(object); /* fetch the object prepared in previous INIT_METHOD opcode and affect it to EG(This) */
    EG(scope) = (fbc->type == ZEND_USER_FUNCTION || !EX(object)) ? fbc->common.scope : NULL;
    EG(called_scope) = EX(call)->called_scope;
}

在實(shí)際執(zhí)行方法體中的代碼時(shí)叔锐,如果出現(xiàn)使用 $this 進(jìn)行方法調(diào)用或?qū)傩再x值的情況挪鹏,如 $this->a = 8 對(duì)應(yīng)的將執(zhí)行 OPCode ZEND_ASSIGN_OBJ,此時(shí)將從 EG(This) 取得 $this 的值

static zend_always_inline zval **_get_obj_zval_ptr_ptr_unused(TSRMLS_D)
{
    if (EXPECTED(EG(This) != NULL)) {
        return &EG(This);
    } else {
        zend_error_noreturn(E_ERROR, "Using $this when not in object context");
        return NULL;
    }
}

Zend 引擎在構(gòu)建方法堆棧時(shí)愉烙,$this 會(huì)被存入符號(hào)表讨盒,就像其他的變量一樣。這樣步责,當(dāng)使用 $this 進(jìn)行方法調(diào)用或?qū)?$this 作為方法的參數(shù)時(shí)返顺,Zend 引擎將從符號(hào)表中獲取 $this

if (op_array->this_var != -1 && EG(This)) {
    Z_ADDREF_P(EG(This)); /* For $this pointer */
    if (!EG(active_symbol_table)) {
        EX_CV(op_array->this_var) = (zval **) EX_CV_NUM(execute_data, op_array->last_var + op_array->this_var);
        *EX_CV(op_array->this_var) = EG(This);
    } else {
        if (zend_hash_add(EG(active_symbol_table), "this", sizeof("this"), &EG(This), sizeof(zval *), (void **) EX_CV_NUM(execute_data, op_array->this_var))==FAILURE) {
            Z_DELREF_P(EG(This));
        }
    }
}

最后是關(guān)于作用域的問題勺择,當(dāng)進(jìn)行方法調(diào)用時(shí)创南,Zend 引擎會(huì)將作用域設(shè)置為 EG(scope)。EG(scope) 是 zend_class_entry 類型省核,也就是說稿辙,在方法中任何關(guān)于 object 的操作的作用域都是 object 對(duì)應(yīng)的 class。對(duì)屬性的訪問控制的檢查也是同樣:

ZEND_API int zend_check_protected(zend_class_entry *ce, zend_class_entry *scope) 
{
    zend_class_entry *fbc_scope = ce;

    /* Is the context that's calling the function, the same as one of
    * the function's parents?
    */
    while (fbc_scope) {
        if (fbc_scope==scope) {
            return 1;
        }
        fbc_scope = fbc_scope->parent;
    }

    /* Is the function's scope the same as our current object context,
    * or any of the parents of our context?
    */
    while (scope) {
        if (scope==ce) {
            return 1;
        }
        scope = scope->parent;
    }
    return 0;
}

static zend_always_inline int zend_verify_property_access(zend_property_info *property_info, zend_class_entry *ce TSRMLS_DC)
{
    switch (property_info->flags & ZEND_ACC_PPP_MASK) {
        case ZEND_ACC_PUBLIC:
            return 1;
        case ZEND_ACC_PROTECTED:
            return zend_check_protected(property_info->ce, EG(scope));
        case ZEND_ACC_PRIVATE:
            if ((ce==EG(scope) || property_info->ce == EG(scope)) && EG(scope)) {
                return 1;
            } else {
                return 0;
            }
            break;
    }
    return 0;
}

正是由于上述特性气忠,所以以下代碼可以正常運(yùn)行

class A
{
    private $a;

    public function foo(A $obj)
    {
        $this->a = 'foo';
        $obj->a  = 'bar'; /* yes, this is possible */
    }
}

$a = new A;
$b = new A;
$a->foo($b);

PHP 中 object 的作用域是 object 對(duì)應(yīng)的 class

⒌ 析構(gòu)方法 destruct

在 PHP 中邻储,不要依賴 destruct 方法銷毀 object赋咽。因?yàn)楫?dāng) PHP 發(fā)生致命錯(cuò)誤時(shí),destruct 方法并不會(huì)被調(diào)用吨娜。

ZEND_API void zend_hash_reverse_apply(HashTable *ht, apply_func_t apply_func TSRMLS_DC)
{
    Bucket *p, *q;

    IS_CONSISTENT(ht);

    HASH_PROTECT_RECURSION(ht);
    p = ht->pListTail;
    while (p != NULL) {
        int result = apply_func(p->pData TSRMLS_CC);

        q = p;
        p = p->pListLast;
        if (result & ZEND_HASH_APPLY_REMOVE) {
            zend_hash_apply_deleter(ht, q);
        }
        if (result & ZEND_HASH_APPLY_STOP) {
            break;
        }
    }
    HASH_UNPROTECT_RECURSION(ht);
}

static int zval_call_destructor(zval **zv TSRMLS_DC) 
{
    if (Z_TYPE_PP(zv) == IS_OBJECT && Z_REFCOUNT_PP(zv) == 1) {
        return ZEND_HASH_APPLY_REMOVE;
    } else {
        return ZEND_HASH_APPLY_KEEP;
    }
}

void shutdown_destructors(TSRMLS_D) 
{
    zend_try {
        int symbols;
        do {
            symbols = zend_hash_num_elements(&EG(symbol_table));
            zend_hash_reverse_apply(&EG(symbol_table), (apply_func_t) zval_call_destructor TSRMLS_CC);
        } while (symbols != zend_hash_num_elements(&EG(symbol_table)));
        zend_objects_store_call_destructors(&EG(objects_store) TSRMLS_CC);
    } zend_catch {
        /* if we couldn't destruct cleanly, mark all objects as destructed anyway */
        zend_objects_store_mark_destructed(&EG(objects_store) TSRMLS_CC);
    } zend_end_try();
}

在調(diào)用 destruct 方法時(shí)脓匿,首先會(huì)從后往前遍歷整個(gè)符號(hào)表,調(diào)用所有引用計(jì)數(shù)為 1 的 object 的 destruct 方法宦赠;然后從前往后遍歷全局 object store陪毡,調(diào)用每個(gè) object 的 destruct 方法。在此過程中如果有任何錯(cuò)誤發(fā)生勾扭,就會(huì)停止調(diào)用 destruct 方法毡琉,然后將所有 object 的 destruct 方法都標(biāo)記為已調(diào)用過的狀態(tài)。

class Foo { public function __destruct() { var_dump("destroyed Foo"); } }
class Bar { public function __destruct() { var_dump("destroyed Bar"); } }

// 示例 1
$a = new Foo;
$b = new Bar;
"destroyed Bar"
"destroyed Foo"

// 示例 2
$a = new Bar;
$b = new Foo;
"destroyed Foo"
"destroyed Bar"

// 示例 3
$a = new Bar;
$b = new Foo;
$c = $b; /* $b 引用計(jì)數(shù)加 1 */
"destroyed Bar"
"destroyed Foo"

// 示例 4
class Foo { public function __destruct() { var_dump("destroyed Foo"); die();} } /* notice the die() here */
class Bar { public function __destruct() { var_dump("destroyed Bar"); } }

$a = new Foo;
$a2 = $a;
$b = new Bar;
$b2 = $b;

"destroyed Foo"

另外妙色,不要在 destruct 方法中添加任何重要的代碼

class Foo
{
    public function __destruct() { new Foo; } /* PHP 最終將崩潰 */
}

PHP 中對(duì)象的銷毀分為兩個(gè)階段:首先調(diào)用 destruct 方法(zend_object_store_bucket->bucket->obj->zend_objects_store_dtor_t)桅滋,然后再釋放內(nèi)存(zend_object_store_bucket->bucket->obj->zend_objects_free_object_storage_t)。

之所以分為兩個(gè)階段執(zhí)行是因?yàn)?destruct 中執(zhí)行的是用戶級(jí)的代碼身辨,即 PHP 代碼丐谋;而釋放內(nèi)存的代碼在系統(tǒng)底層運(yùn)行。釋放內(nèi)存會(huì)破壞 PHP 的運(yùn)行環(huán)境煌珊,為了使 destruct 中的 PHP 代碼能正常運(yùn)行号俐,所以分為兩個(gè)階段,這樣怪瓶,保證在釋放內(nèi)存階段 object 已經(jīng)不被使用萧落。

三、PHP 7 中的 object

與 PHP 5 相比洗贰,PHP 7 中的 object 在用戶層并沒有基本沒有什么變化找岖;但在底層實(shí)現(xiàn)上,在內(nèi)存和性能方面做了一些優(yōu)化敛滋。

⒈ 在內(nèi)存布局和管理上的優(yōu)化

① 首先许布,在 zval 中移除了之前的 zend_object_value 結(jié)構(gòu),直接嵌入了 zend_object绎晃。這樣蜜唾,既節(jié)省了內(nèi)存空間,同時(shí)提高了通過 zval 查找 zend_object 的效率

/*PHP 7 中的 zend_object*/
struct _zend_object {
    zend_refcounted   gc;
    uint32_t          handle;
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];
};

/*PHP 5 中的 zend_object_value*/
typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

在 PHP 5 中通過 zval 訪問 object庶艾,先要通過 zva 中的 zend_object_value 找到 handle袁余,然后通過handle 在 zend_object_store 中找到 zend_object_store_bucket,然后從 bucket 中解析出 object咱揍。在 PHP 7 中颖榜,zval 中直接存儲(chǔ)了 zend_object 的地址指針。

② 其次,properties_table 利用了 struct hack 特性掩完,這樣使得 zend_object 和 properties_table 存儲(chǔ)在一塊連續(xù)的內(nèi)存空間噪漾。同時(shí),properties_table 中直接存儲(chǔ)了屬性的 zval 結(jié)構(gòu)且蓬。

③ guards 不再出現(xiàn)在 zend_object 中欣硼。如果 class 中定義了魔術(shù)方法( __set__get恶阴、__isset诈胜、__unset ),則 guards 存儲(chǔ)在 properties_table 的第一個(gè) slot 中冯事;否則不存儲(chǔ) guards耘斩。

④ zend_object_store 及 zend_object_store_bucket 被移除,取而代之的是一個(gè)存儲(chǔ)各個(gè) zend_object 指針的 C 數(shù)組桅咆,handle 為數(shù)組的索引。此外坞笙,之前 bucket 中存儲(chǔ)的 handlers 現(xiàn)在移入 zend_object 中岩饼;而之前 bucket 中的 dtor、free_storege薛夜、clone 現(xiàn)在則移入了 zend_object_handlers籍茧。

struct _zend_object_handlers {
    /* offset of real object header (usually zero) */
    int                                     offset;
    /* general object functions */
    zend_object_free_obj_t                  free_obj;
    zend_object_dtor_obj_t                  dtor_obj;
    zend_object_clone_obj_t                 clone_obj;
    /* individual object functions */
    // ... 其他與 PHP 5 相同
};

⒉ 底層自定義 object 的變化(PHP 擴(kuò)展中會(huì)用到自定義 object)

/*PHP 5 中的 custom_object*/
struct custom_object {
    zend_object std;
    my_custom_type *my_buffer;
    // ...
};

/*PHP 7 中的 custom_object*/
struct custom_object {
    my_custom_type *my_buffer;
    // ...
    zend_object std;
};

由于 PHP 7 的 zend_object 中使用了 struct hack 特性來保證 zend_object 內(nèi)存的連續(xù),所以自定義 object 中的 zend_object 只能放在最后梯澜。而 zval 中存儲(chǔ)的只能是 zend_object寞冯,為了能通過 zend_object 順利解析出 custom_object ,在 zend_object 的 handlers 中記錄了 offset晚伙。

4.png

本文轉(zhuǎn)載自:https://www.php.cn/php-weizijiaocheng-481600.html

推薦學(xué)習(xí):《PHP視頻教程

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吮龄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子咆疗,更是在濱河造成了極大的恐慌漓帚,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件午磁,死亡現(xiàn)場(chǎng)離奇詭異尝抖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)迅皇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門昧辽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人登颓,你說我怎么就攤上這事搅荞。” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵取具,是天一觀的道長(zhǎng)脖隶。 經(jīng)常有香客問我,道長(zhǎng)暇检,這世上最難降的妖魔是什么产阱? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任构蹬,我火速辦了婚禮庄敛,結(jié)果婚禮上科汗,老公的妹妹穿的比我還像新娘藻烤。我一直安慰自己,他們只是感情好头滔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布怖亭。 她就那樣靜靜地躺著,像睡著了一般坤检。 火紅的嫁衣襯著肌膚如雪兴猩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天早歇,我揣著相機(jī)與錄音倾芝,去河邊找鬼。 笑死箭跳,一個(gè)胖子當(dāng)著我的面吹牛晨另,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播衅码,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼拯刁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了逝段?” 一聲冷哼從身側(cè)響起垛玻,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎账嚎,沒想到半個(gè)月后疼邀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梢薪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了赴邻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞎暑。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逢并,到底是詐尸還是另有隱情,我是刑警寧澤玻蝌,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布许饿,位于F島的核電站胸完,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锨能。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一倔约、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦重罪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)漾抬。三九已至纳令,卻和暖如春漠另,著一層夾襖步出監(jiān)牢的瞬間性湿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留骇扇,地道東北人继低。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像柄驻,于是被迫代替她去往敵國(guó)和親抑钟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

推薦閱讀更多精彩內(nèi)容