iOS內存管理—內存管理方案(sideTable)

上篇文章介紹了內存管理方案中的Tagged Pointer 小對象類型沧侥,這篇文章來介紹下另一種方案sideTable 散列表

struct SideTable {
    spinlock_t slock;
    // 引用計數表
    RefcountMap refcnts;
    //弱引用表
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

由上述代碼得知散列表其實就是個結構體,我們發(fā)現有refcnts和weak_table這兩張表

引用計數

retain源碼分析

進入objc_retain -> retain -> rootRetain源碼實現

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
    //需要對引用計數+1鸽照,即retain+1与境,而引用計數存儲在isa的bits中兰珍,需要進行新舊isa的替換,所以這里需要isa
    isa_t oldisa;
    isa_t newisa;
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判斷是否為nonpointer isa
        if (slowpath(!newisa.nonpointer)) {
            //如果不是 nonpointer isa洞拨,直接操作散列表sidetable
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        //是否正在析構
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        
        
        uintptr_t carry;
        //執(zhí)行引用計數+1操作剧劝,即對bits中的 1ULL<<45(arm64) 即extra_rc雁芙,用于該對象存儲引用計數值
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++
        //判斷extra_rc是否滿了,carry是標識符
        if (slowpath(carry)) {
            // newisa.extra_rc++ overflowed
            if (!handleOverflow) {
                ClearExclusive(&isa.bits);
                return rootRetain_overflow(tryRetain);
            }
            // Leave half of the retain counts inline and 
            // prepare to copy the other half to the side table.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            //如果extra_rc滿了熬荆,則直接將滿狀態(tài)的一半拿出來存到extra_rc
            newisa.extra_rc = RC_HALF;
            //給一個標識符為YES舟山,表示需要存儲到散列表
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        //將另一半存在散列表中
        sidetable_addExtraRC_nolock(RC_HALF);
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}
  • 判斷是否是Nonpointer_isa,如果不是直接往散列表中存
  • 判斷是否正在釋放,如果正在釋放累盗,執(zhí)行dealloc流程
  • 執(zhí)行extra_rc+1寒矿,即引用計數+1操作,并給一個引用計數的狀態(tài)標識carry若债,用于表示extra_rc是否滿了
  • 如果是carray狀態(tài)符相,表示extra_rc已經存滿,這時需要忘散列表中存拆座。即將滿狀態(tài)的計數一半存入extra_rc主巍,一半存入散列表冠息。這樣做是因為都存入散列表中挪凑,每次對散列表操作都需要開解鎖,操作耗時逛艰,消耗性能大躏碳,這么對半分操作的目的在于提高性能
release源碼分析
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //判斷是否是Nonpointer isa
        if (slowpath(!newisa.nonpointer)) {
            //如果不是,則直接操作散列表-1
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        uintptr_t carry;
        //進行引用計數-1操作散怖,即extra_rc-1
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        //如果此時extra_rc的值為0了菇绵,則走到underflow
        if (slowpath(carry)) {
            // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;
    //散列表中是否存儲了一半的引用計數
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.

        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        //從散列表中取出存儲的一半引用計數
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.

        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            //進行-1操作,然后存儲到extra_rc中
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }
    //此時extra_rc中值為0镇眷,散列表中也是空的咬最,則直接進行析構,即自動觸發(fā)dealloc流程
    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        //發(fā)送一個dealloc消息
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}


  • 判斷是否是Nonpointer isa欠动,如果不是永乌,則直接對散列表進行-1操作
  • extra_rc中的引用計數值進行-1操作,并存儲此時的extra_rc狀態(tài)到carry中
  • 如果此時的狀態(tài)carray為0具伍,則走到underflow流程
  • 判斷散列表中是否存儲了一半的引用計數`
  • 如果是翅雏,則從散列表中取出存儲的一半引用計數,進行-1操作人芽,然后存儲到extra_rc
  • 如果此時extra_rc沒有值望几,散列表中也是空的,則直接進行析構

弱引用分析

先來看一個案例

    NSObject *shObjc = [[NSObject alloc] init];
    NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(shObjc)),shObjc,&shObjc);
    __weak typeof(id) weakObj = shObjc;
    NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(shObjc)),shObjc,&shObjc);
    NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakObj)),weakObj,&weakObj);

image.png

接下來分析下這個案例

  • 第一個NSLog,引用計數等于1這里沒有問題
  • 第二個NSLog,引用計數等于1這里也沒有問題
  • 第三個NSLog,引用計數等于2這里就有問題了萤厅。我們知道weak修飾的變量橄抹,是弱引用引用計數是不增加的惕味,那么這里為什么等于2呢楼誓?

接下來分析下源碼,在weak聲明處下一個斷點

image.png

匯編處發(fā)現了objc_initWeak方法
image.png

查看源碼赦拘,進入objc_initWeak方法慌随,在這里發(fā)現不論是初始化weak,置空還是銷毀方法,都調用了storeWeak方法,所以這個方法是個高度封裝的方法
image.png

進入storeWeak

static id 
storeWeak(id *location, objc_object *newObj)
{
    ASSERT(haveOld  ||  haveNew);
    if (!haveNew) ASSERT(newObj == nil);

    Class previouslyInitializedClass = nil;
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;

    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry:
    if (haveOld) {
         oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);

    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // Clean up old value, if any.
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }

    // Assign new value, if any.
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (!newObj->isTaggedPointerOrNil()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);

    // This must be called without the locks held, as it can invoke
    // arbitrary code. In particular, even if _setWeaklyReferenced
    // is not implemented, resolveInstanceMethod: may be, and may
    // call back into the weak reference machinery.
    callSetWeaklyReferenced((id)newObj);

    return (id)newObj;
}
  • 這里有兩個入參location (聲明的weakObj的指針地址)newObj (需要綁定的對象)
  • 如果haveOld存在阁猜,則對弱引用表進行移出丸逸,說明haveOld代表的是移出操作。如果haveNew存在,則進行對象注冊進弱引用表
    image.png

進入weak_register_no_lock方法

id 
weak_register_no_lock(weak_table_t *weak_table, id referent_id, 
                      id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
    // 對象
    objc_object *referent = (objc_object *)referent_id;
    // 對象的弱引用指針地址
    objc_object **referrer = (objc_object **)referrer_id;
    /// 如果是小對象類型直接返回對象
    if (referent->isTaggedPointerOrNil()) return referent_id;

    // ensure that the referenced object is viable
    // 如果正在析構剃袍,進行析構處理
    if (deallocatingOptions == ReturnNilIfDeallocating ||
        deallocatingOptions == CrashIfDeallocating) {
        bool deallocating;
        if (!referent->ISA()->hasCustomRR()) {
            deallocating = referent->rootIsDeallocating();
        }
        else {
            // Use lookUpImpOrForward so we can avoid the assert in
            // class_getInstanceMethod, since we intentionally make this
            // callout with the lock held.
            auto allowsWeakReference = (BOOL(*)(objc_object *, SEL))
            lookUpImpOrForwardTryCache((id)referent, @selector(allowsWeakReference),
                                       referent->getIsa());
            if ((IMP)allowsWeakReference == _objc_msgForward) {
                return nil;
            }
            deallocating =
            ! (*allowsWeakReference)(referent, @selector(allowsWeakReference));
        }

        if (deallocating) {
            if (deallocatingOptions == CrashIfDeallocating) {
                _objc_fatal("Cannot form weak reference to instance (%p) of "
                            "class %s. It is possible that this object was "
                            "over-released, or is in the process of deallocation.",
                            (void*)referent, object_getClassName((id)referent));
            } else {
                return nil;
            }
        }
    }

    // now remember it and where it is being stored
    //entry 是個數組黄刚,里面存儲對象
    weak_entry_t *entry;
    // 如果entry 中有這個對象,將這個弱引用指針加入到對象中
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        append_referrer(entry, referrer);
    } 
    else {
        // 否則創(chuàng)建一個新的entry,將對象加入entry民效,并將弱引用指針添加到對象中
        weak_entry_t new_entry(referent, referrer);
        weak_grow_maybe(weak_table);
        // 將entry添加到弱引用表中
        weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the 
    // value not change.

    return referent_id;
}
  • 這里首先將傳入的參數轉換成objc_object的對象和 弱引用指針
  • 如果是小對象類型直接return
  • 接下來判斷是否正在析構
  • 判斷entry是否有傳入的對象
  • 如果有則將這個弱引用指針加入到對象中
  • 如果沒有憔维,則創(chuàng)建一個entry,并將對象加入到entry中,弱引用指針加入到對象

查看append_referrer

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
    if (! entry->out_of_line()) {
        // Try to insert inline.`
        
        // 將 對象放入entry 中空閑的節(jié)點
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            if (entry->inline_referrers[i] == nil) {
                entry->inline_referrers[i] = new_referrer;
                return;
            }
        }
        
        // Couldn't insert inline. Allocate out of line.
        weak_referrer_t *new_referrers = (weak_referrer_t *)
            calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
        // This constructed table is invalid, but grow_refs_and_insert
        // will fix it and rehash it.
        for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
            new_referrers[i] = entry->inline_referrers[I];
        }
        entry->referrers = new_referrers;
        entry->num_refs = WEAK_INLINE_COUNT;
        entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
        entry->mask = WEAK_INLINE_COUNT-1;
        entry->max_hash_displacement = 0;
    }

    ASSERT(entry->out_of_line());

    // 對 entry 進行擴容
    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
        return grow_refs_and_insert(entry, new_referrer);
    }
    size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
    size_t index = begin;
    size_t hash_displacement = 0;
    while (entry->referrers[index] != nil) {
        hash_displacement++;
        index = (index+1) & entry->mask;
        if (index == begin) bad_weak_table(entry);
    }
    if (hash_displacement > entry->max_hash_displacement) {
        entry->max_hash_displacement = hash_displacement;
    }
    //
    weak_referrer_t &ref = entry->referrers[index];
    ref = new_referrer;
    entry->num_refs++;
}
  • entry默認是開辟了4個空間
  • 遍歷ntry->inline_referrers是否有空閑的畏邢,如果有直接將new_referrer放入
  • 如果沒有則要對entry進行擴容
WX20210914-111843@2x.png

由上圖得知弱引用表的結構為业扒,且都是1對多的關系
散列表->弱引用表->entry -> 對象->弱引用指針

  • 通過SideTable得到weakTable
  • 判斷weakTable是否有該對象的weak_entry_t
  • 如果沒有,創(chuàng)建weak_entry_t
  • referrer 弱引用指針地址加入到weak_entry_t 數組中inline_referrers `
  • 如果weak_entry_t空間不夠舒萎,則進行擴容
  • 并把new_entry加入到weak_table

回到剛開始介紹弱引用表的案例程储,并在第三個NSLog處打一個斷點

image.png

在匯編代碼中發(fā)現了objc_loadWeakRetained這個方法
image.png

進入objc_loadWeakRetained

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    obj = *location;
    if (obj->isTaggedPointerOrNil()) return obj;
    
    table = &SideTables()[obj];
    
    table->lock();
    if (*location != obj) {
        table->unlock();
        goto retry;
    }
    
    result = obj;

    cls = obj->ISA();
    if (! cls->hasCustomRR()) {
        // Fast case. We know +initialize is complete because
        // default-RR can never be set before then.
        ASSERT(cls->isInitialized());
        if (! obj->rootTryRetain()) {
            result = nil;
        }
    }
    else {
        // Slow case. We must check for +initialize and call it outside
        // the lock if necessary in order to avoid deadlocks.
        // Use lookUpImpOrForward so we can avoid the assert in
        // class_getInstanceMethod, since we intentionally make this
        // callout with the lock held.
        if (cls->isInitialized() || _thisThreadIsInitializingClass(cls)) {
            BOOL (*tryRetain)(id, SEL) = (BOOL(*)(id, SEL))
                lookUpImpOrForwardTryCache(obj, @selector(retainWeakReference), cls);
            if ((IMP)tryRetain == _objc_msgForward) {
                result = nil;
            }
            else if (! (*tryRetain)(obj, @selector(retainWeakReference))) {
                result = nil;
            }
        }
        else {
            table->unlock();
            class_initialize(cls, obj);
            goto retry;
        }
    }
        
    table->unlock();
    return result;
}
  • 傳入的location是我們的weakObj
  • obj = *location;weakObj 指向的內存,也就是shObjc

我們在這打個斷點臂寝,并打印obj的引用計數章鲤,這個時候為1

image.png

在經過rootTryRetain方法后引用計數就變?yōu)榱?code>2
image.png

進入rootTryRetain,發(fā)現這里進入了rootRetain,這里我們在引用計數時已經分析了

image.png

  • 也就是說弱引用對象也會造成引用計數的增加
  • 但是這里和我們平時所掌握的weak修飾的對象咆贬,引用計數不會增加败徊,有出入

接下來我們打印多次weakObj,發(fā)現引用計數始終為2掏缎,這里并沒有增加

image.png

是因為objc_loadWeakRetained調用這個方法時,result是一個臨時變量皱蹦,該方法最終也是return result,這也就導致當去打印weakObj的引用計數為2,但是當離開作用域后,就會去釋放引用計數

id
objc_loadWeakRetained(id *location)
{
    id obj;
    id result;
    Class cls;

    SideTable *table;
    
 retry:
    // fixme std::atomic this load
    obj = *location;
    ...
    table = &SideTables()[obj];
    ...
    result = obj;
...
    return result;
}

再來看一個案例

    __weak typeof(id) weakSelfObj ;
    
    {
        NSObject *objc = [[NSObject alloc] init];
        weakSelfObj = objc;
        NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(objc)),objc,&objc);
        
        weakSelfObj = objc;
        NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(objc)),objc,&objc);
        NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakSelfObj)),weakSelfObj,&weakSelfObj);
        

        
    }
    NSLog(@"%zd---%@---%p",CFGetRetainCount((__bridge CFTypeRef)(weakSelfObj)),weakSelfObj,&weakSelfObj);
    

運行后發(fā)現崩潰了


image.png

是因為在作用域內部時御毅,weakSelfObj 和objc指向同一片內存

image.png

出了作用域weakSelfObj指向的內存已經被釋放根欧,因為這里weakSelfObj是弱持有指向的內存objc是強持有內存端蛆,但是objc是在作用域內部聲明凤粗,所以出了作用域指向的內存會被釋放,所以這里也就崩潰了
image.png

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末今豆,一起剝皮案震驚了整個濱河市嫌拣,隨后出現的幾起案子,更是在濱河造成了極大的恐慌呆躲,老刑警劉巖异逐,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異插掂,居然都是意外死亡灰瞻,警方通過查閱死者的電腦和手機腥例,發(fā)現死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酝润,“玉大人燎竖,你說我怎么就攤上這事∫” “怎么了构回?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長疏咐。 經常有香客問我纤掸,道長,這世上最難降的妖魔是什么浑塞? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任借跪,我火速辦了婚禮,結果婚禮上缩举,老公的妹妹穿的比我還像新娘垦梆。我一直安慰自己匹颤,他們只是感情好仅孩,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著印蓖,像睡著了一般辽慕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赦肃,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天溅蛉,我揣著相機與錄音,去河邊找鬼他宛。 笑死船侧,一個胖子當著我的面吹牛,可吹牛的內容都是我干的厅各。 我是一名探鬼主播镜撩,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼队塘!你這毒婦竟也來了袁梗?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤憔古,失蹤者是張志新(化名)和其女友劉穎遮怜,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體鸿市,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡锯梁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年即碗,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陌凳。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拜姿,死狀恐怖,靈堂內的尸體忽然破棺而出冯遂,到底是詐尸還是另有隱情蕊肥,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布蛤肌,位于F島的核電站壁却,受9級特大地震影響,放射性物質發(fā)生泄漏裸准。R本人自食惡果不足惜展东,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望炒俱。 院中可真熱鬧盐肃,春花似錦、人聲如沸权悟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽峦阁。三九已至谦铃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間榔昔,已是汗流浹背驹闰。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撒会,地道東北人嘹朗。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像诵肛,于是被迫代替她去往敵國和親屹培。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容