Objective-C 小記(8)autorelease

本文使用的 runtime 版本為 objc4-706

對于 autorelease 的研究需要先從 @autoreleasepool { ... } 著手课兄。首先對有 @autoreleasepool { ... } 的代碼使用 clang -rewrite-objc 進行轉換萎胰,在轉換后的文件中靠汁,可以看到 @autoreleasepool { ... } 變成了這樣:

/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
    ...
}

當然還可以找到 __AtAutoreleasePool 的定義:

struct __AtAutoreleasePool {
    __AtAutoreleasePool() {
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() {
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
};

可以看到王暗,代碼利用了變量聲明和自動變量在代碼塊結束后自動銷毀的特性姊扔,在構造函數(shù)和析構函數(shù)中調用了 objc_autoreleasePoolPushobjc_autoreleasePoolPop 函數(shù)鳖枕。在 NSObject.mm 文件中可以找到這兩個函數(shù)的實現(xiàn):

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

可以發(fā)現(xiàn)魄梯,這兩個函數(shù)只是對 AutoreleasePoolPage 這個 C++ 類的兩個類方法 pushpop 的簡單封裝。

AutoreleasePoolPage

NSObject.mm 中可以找到 AutoreleasePoolPage 類的實現(xiàn)宾符,先可以看一下它的成員變量:

class AutoreleasePoolPage
{
    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}

一個一個過一下這些成員變量:

  1. magic:這個變量的類型是 magic_t酿秸,是用來檢查 AutoreleasePoolPage 的內存沒有被修改的,放在第一個也就是這個原因魏烫,防止前面地址有內容溢過來辣苏。
  2. next:類型是 id *肝箱,存放的是下一個被 autorelease 的對象指針存放的地址。
  3. thread:對應的線程稀蟋,這說明了自動釋放池是對應線程的煌张。
  4. parentchild:用來保存前一個 AutoreleasePoolPage 和后一個 AutoreleasePoolPage,就是一個雙向鏈表退客,畢竟一個 AutoreleasePoolPage 能存放的對象是有限的骏融。
  5. depth:很明顯是這個鏈表有多深。
  6. hiwat:一個在 DEBUG 時才有用的參數(shù)井辜,表示最高有記錄過多少對象(hi-water)绎谦。

可以注意到,這些成員變量并沒有指示出對象記錄在哪里粥脚,繼續(xù)在 AutoreleasePoolPage 的實現(xiàn)里看一看窃肠,能發(fā)現(xiàn)一些有趣的東西:

    static size_t const SIZE = 
        PAGE_MAX_SIZE;  // size and alignment, power of 2
    
    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }

AutoreleasePoolPage 重載了 new 操作符,這樣一個新的對象需要 SIZE 這么多的內存空間刷允,SIZE 的值 PAGE_MAX_SIZE 是一個根據(jù)機器不同的大小冤留,在寫這篇文章的機器上(i386)上是 4096。AutoreleasePoolPage 的成員變量大小加在一起也只有 56 字節(jié)树灶,但是 new 它一個居然要 4096 字節(jié)纤怒,這剩下的 4040 字節(jié)肯定就是存放被 autorelease 的對象的地方了。AutoreleasePoolPage 的實現(xiàn)中有這些個函數(shù):

    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

    bool empty() {
        return next == begin();
    }

    bool full() { 
        return next == end();
    }

    bool lessThanHalfFull() {
        return (next - begin() < (end() - begin()) / 2);
    }

可以看到天通,begin 就是成員變量結束的地址(this+sizeof(*this))泊窘,end 就是整個申請的內存結束的地方了,其余的函數(shù)很好看懂像寒。對于成員變量 next 來說烘豹,可以看一下構造函數(shù):

    AutoreleasePoolPage(AutoreleasePoolPage *newParent) 
        : magic(), next(begin()), thread(pthread_self()),
          parent(newParent), child(nil), 
          depth(parent ? 1+parent->depth : 0), 
          hiwat(parent ? parent->hiwat : 0)
    { 
        if (parent) {
            parent->check();
            assert(!parent->child);
            parent->unprotect();
            parent->child = this;
            parent->protect();
        }
        protect();
    }

可以看到 next(begin())next 的初始值就是 begin诺祸。結合上面對 next 的描述携悯,就能理解這個初始值的意義了。

autorelease

現(xiàn)在來研究一下 autorelease 是怎么實現(xiàn)的筷笨,autorelease 的入口是 objc_autorelease 函數(shù):

__attribute__((aligned(16)))
id
objc_autorelease(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->autorelease();
}

就是很簡單的進行了判空和判斷 tagged pointer 后憔鬼,就將實現(xiàn)交給了 objc_object 結構體的 autorelease 函數(shù):

// Equivalent to [this autorelease], with shortcuts if there is no override
inline id 
objc_object::autorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (fastpath(!ISA()->hasCustomRR())) return rootAutorelease();

    return ((id(*)(objc_object *, SEL))objc_msgSend)(this, SEL_autorelease);
}

走的也是 reatinrelease 的老套路,如果沒有自定義的實現(xiàn)胃夏,就走默認實現(xiàn) rootAutorelease轴或,否則直接給自定義實現(xiàn)發(fā)消息。繼續(xù)查看默認實現(xiàn):

// Base autorelease implementation, ignoring overrides.
inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

其中 prepareOptimizedReturn 函數(shù)是 ARC 對 autorelease 的優(yōu)化构订,本篇文章不做研究侮叮,繼續(xù)查看 rootAutorelease2

__attribute__((noinline,used))
id 
objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

果不其然,是調用了 AutoreleasePoolPage 里的實現(xiàn)(這不廢話嗎前面還講了那么多關于 AutoreleasePoolPage 手動捂臉)悼瘾。

繼續(xù)追查 autorelease 函數(shù):

    static inline id autorelease(id obj)
    {
        assert(obj);
        assert(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

可以看到囊榜,實際工作交給了 autoreleaseFast 函數(shù),文章之后再對這個函數(shù)繼續(xù)分析亥宿。

push

結合文章最開始的分析卸勺,push 函數(shù)就是往 AutoreleasePoolPage 這一整個內存空間里壓入一個自動釋放池,看一下 push 的實現(xiàn):

#   define POOL_BOUNDARY nil

    static inline void *push() 
    {
        id *dest = autoreleaseFast(POOL_BOUNDARY);
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

push 的實現(xiàn)里很有意思的往 autoreleaseFast 函數(shù)里傳入了一個叫 POOL_BOUNDARY(池邊界)的東西烫扼,可以看到它其實就是 nil曙求。新建一個自動釋放池為什么要和 autorelease 調用一樣的函數(shù)呢?接下來分析一下 autoreleaseFast 函數(shù)映企。

autoreleaseFast

    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) {
            return autoreleaseFullPage(obj, page);
        } else {
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFast 中悟狱,首先需要拿到一個 hot page,這個其實就是所在線程正在使用的 AutoreleasePoolPage堰氓,hotPage 的實現(xiàn)有一點需要注意的地方:

    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }

hotPage 的實現(xiàn)很簡單挤渐,使用 tls_get_direct 獲得線程(TLS, Thread-local storage)的 AutoreleasePoolPage 對象,fastcheck 是對 magic 的檢查双絮,但是如果發(fā)現(xiàn)結果是 EMPTY_POOL_PLACEHOLDER 也就是 1 的話浴麻,也返回 nil

EMPTY_POOL_PLACEHOLDER 從注釋的說明可以知道囤攀,是對當只有一個自動釋放池創(chuàng)建了(push 了)并且沒有任何對象被 autorelease 時的優(yōu)化∪砻猓現(xiàn)在只需要知道它的存在就好。

回到 autoreleaseFast 函數(shù)焚挠,在拿到 page 后膏萧,需要對 page 的不同情況進行不同的處理。

先看最簡單的情況蝌衔,也就是有 hot page 并且它沒有滿的情況榛泛,這個時候調用了 pageadd 方法:

    id *add(id obj)
    {
        assert(!full());
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        return ret;
    }

可以看到,這就是將 obj 存到 next 的位置胚委,并將 next 加 1挟鸠,典型的入棧操作。如果 obj 是一個對象(autorelease 方法的調用)亩冬,這就是將對象保存在自動釋放池了艘希,如果 objPOOL_BOUNDARY 也就是 nilpush 方法的調用)則這里便是自動釋放池的分界。

繼續(xù)看 pagenil 的情況硅急,也就是對 autoreleaseNoPage 函數(shù)的調用:

    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // "No page" could mean no pool has been pushed
        // or an empty placeholder pool has been pushed and has no contents yet
        assert(!hotPage());

        bool pushExtraBoundary = false;
        if (haveEmptyPoolPlaceholder()) {
            // We are pushing a second pool over the empty placeholder pool
            // or pushing the first object into the empty placeholder pool.
            // Before doing that, push a pool boundary on behalf of the pool 
            // that is currently represented by the empty placeholder.
            pushExtraBoundary = true;
        }
        else if (obj == POOL_BOUNDARY) {
            // We are pushing a pool with no pool in place,
            // and alloc-per-pool debugging was not requested.
            // Install and return the empty pool placeholder.
            return setEmptyPoolPlaceholder();
        }

        // We are pushing an object or a non-placeholder'd pool.

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);
    }

進入到這個函數(shù)覆享,會有兩種情況,注釋里也已經說明了营袜,也是剛才 hotPage 函數(shù)實現(xiàn)注意的地方撒顿。hot page 是 EMPTY_POOL_PLACEHOLDER 也會被當作是 no page,進入這個函數(shù)荚板。

我們先假設一種情況凤壁,是第一個自動釋放池創(chuàng)建時吩屹,首先對 haveEmptyPoolPlaceholder 函數(shù)的結果進行判斷:

    static inline bool haveEmptyPoolPlaceholder()
    {
        id *tls = (id *)tls_get_direct(key);
        return (tls == EMPTY_POOL_PLACEHOLDER);
    }

這個函數(shù)其實就是判斷 hot page 是不是 EMPTY_POOL_PLACEHOLDER,因為我們現(xiàn)在假設為第一次創(chuàng)建自動釋放池拧抖,所以這個函數(shù)的返回值便是 false煤搜,并且 obj 參數(shù)的值是 POOL_BOUNDARY,因此 autoreleaseNoPage 會調用 setEmptyPoolPlaceholder 并返回唧席,而 setEmptyPoolPlaceholder 的實現(xiàn):

    static inline id* setEmptyPoolPlaceholder()
    {
        assert(tls_get_direct(key) == nil);
        tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
        return EMPTY_POOL_PLACEHOLDER;
    }

就是將 hot page 設置為 EMPTY_POOL_PLACEHOLDER擦盾。這樣,在第一次創(chuàng)建(push)一個自動釋放池時淌哟,并沒有生成 AutoreleasePoolPage 對象迹卢,而是使用了一個占位符。

現(xiàn)在進入第二種情況徒仓,在上面的情況發(fā)生完之后腐碱,有一個對象被 autorelease 了,流程也會進入 autoreleaseNoPage蓬衡,但是現(xiàn)在 haveEmptyPoolPlaceholder 返回的是 true 了喻杈,將會把 pushExtraBoundary 也設置為 true

這樣在接下來的代碼中狰晚,會創(chuàng)建新的對象 page 并將它設置為 hot page筒饰,因為發(fā)現(xiàn) pushExtraBoundaryture,因此還需要 add 一個 POOL_BOUNDARY壁晒。最后再將對象也加入瓷们,就完事了。

最后看到 page 滿了的情況秒咐,也就是對 autoreleaseFullPage 函數(shù)的調用:

    static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

思路很清晰谬晕,就是檢查 page 有沒有還沒滿的 child(順鏈表往下查),沒有的話就新建一個携取,再使用 add 函數(shù)將 obj 記錄攒钳。

pop

其實現(xiàn)在大概能感覺到,自動釋放池其實就是個用鏈表實現(xiàn)的一個棧雷滋。繼續(xù)看 pop 的實現(xiàn)也就一個自動釋放池結束的時候:

    static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        page->releaseUntil(stop);

        // memory: delete empty children
        if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

參數(shù) token不撑,傳入的是 push 返回值,其實就是 push 函數(shù)插入 POOL_BOUNDARY 的地址(指針)晤斩,在 pop 里表示要一直釋放到 token 指向的地址為止焕檬。

pop 函數(shù)一開始會檢查 token 是不是 EMPTY_POOL_PLACEHOLDER。當 tokenEMPTY_POOL_PLACEHOLDER 時澳泵,會繼續(xù)檢查是否有 hot page(理論上來說不應該會有 hot page实愚,一個疑問),如果沒有 hot page,則直接將 hot page 設置為 nil腊敲,如果有 hot page击喂,則重新調用 pop,傳入的 tokencoldPage()-begin()兔仰,coldPage 的實現(xiàn)如下:

    static inline AutoreleasePoolPage *coldPage() 
    {
        AutoreleasePoolPage *result = hotPage();
        if (result) {
            while (result->parent) {
                result = result->parent;
                result->fastcheck();
            }
        }
        return result;
    }

很明顯茫负,所謂的 cold page 就是線程的第一個 AutoreleasePoolPage蕉鸳。

pop 函數(shù)的 token 不是 EMPTY_POOL_PLACEHOLDER 時乎赴,進入正常的 pop 流程,首先要獲取到 token 也就是一個內存地址的所在 page潮尝,也就是 pageForPointer 函數(shù)的工作:

    static AutoreleasePoolPage *pageForPointer(const void *p) 
    {
        return pageForPointer((uintptr_t)p);
    }
    
    static AutoreleasePoolPage *pageForPointer(uintptr_t p) 
    {
        AutoreleasePoolPage *result;
        uintptr_t offset = p % SIZE;

        assert(offset >= sizeof(AutoreleasePoolPage));

        result = (AutoreleasePoolPage *)(p - offset);
        result->fastcheck();

        return result;
    }

因為 AutoreleasePoolPage 對象是根據(jù) SIZE 的大小來對齊的榕吼,所以使用地址 p 的值對 SIZE 取余就能獲取到 p 和所在 page 地址的偏移值(offset),從而得到所在 page勉失,最后會對所在 page 的 magic 進行檢查羹蚣,也就是 fastcheck 所做的工作。

獲得了 page 以后乱凿,pop 函數(shù)還會檢查在 token 這個地址存儲的內容是否是 POOL_BOUNDRAY

        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

正常來說顽素,在這個地方 token 就應該得是 POOL_BOUNDARY,因為 push 函數(shù)每次都是添加的 POOL_BOUNDARY徒蟆。但這個地方進行了判斷胁出,其中如果如果 token 就是 pagebegin,并且 page 是第一個的話段审,則認為是正常情況(這其實是沒有 push 就直接 autorelease 了)全蝶。否則進入 badPop 流程,這個流程會在最新的 SDK (10.12, 10.0, 10.0, 3.0)上會直接產生 fatal寺枉。

接下來的正常流程抑淫,也就是 token 所指向的地址存儲的內容為 POOL_BOUNDARY 時,調用 releaseUntil 函數(shù):

    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));

            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }

        setHotPage(this);
    }

實現(xiàn)很容易看懂姥闪,就是循環(huán)直到到 stop 給每個對象調用 objc_release 也就等同于發(fā)送 release 消息始苇。其中每次都從 hot page 開始的原因注釋里進行了說明,是怕 release 方法里又 autorelease 了對象筐喳。

最后催式,pop 函數(shù)還要刪除不需要的空的 page:

        // memory: delete empty children
        if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }

做了點小優(yōu)化,如果現(xiàn)在這個 page 只剩下不到一半的空間了疏唾,則多留一個 child蓄氧。kill 的實現(xiàn)如下:

    void kill() 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        AutoreleasePoolPage *page = this;
        while (page->child) page = page->child;

        AutoreleasePoolPage *deathptr;
        do {
            deathptr = page;
            page = page->parent;
            if (page) {
                page->child = nil;
            }
            delete deathptr;
        } while (deathptr != this);
    }

其實就是沿著鏈表刪除。

總結

總的來看槐脏,自動釋放池的實現(xiàn)思想是很簡單的:

  1. 對每個線程來說喉童,用一個由 AutoreleasePoolPage 的組成的雙向鏈表維護一個棧,被 autorelease 的對象記錄在這個棧中;
  2. 使用 POOL_BOUNDARY 也就是 nil 來對自動釋放池進行分隔堂氯。

當然蔑担,其中實現(xiàn)還是有著各種有趣的細節(jié)的。

本文原始地址:Objective-C 小記(8)autorelease

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末咽白,一起剝皮案震驚了整個濱河市啤握,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晶框,老刑警劉巖排抬,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異授段,居然都是意外死亡蹲蒲,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門侵贵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來届搁,“玉大人,你說我怎么就攤上這事窍育】溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵漱抓,是天一觀的道長表锻。 經常有香客問我,道長辽旋,這世上最難降的妖魔是什么浩嫌? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮补胚,結果婚禮上码耐,老公的妹妹穿的比我還像新娘。我一直安慰自己溶其,他們只是感情好骚腥,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓶逃,像睡著了一般束铭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上厢绝,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天契沫,我揣著相機與錄音,去河邊找鬼昔汉。 笑死懈万,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播会通,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼口予,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涕侈?” 一聲冷哼從身側響起沪停,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎裳涛,沒想到半個月后木张,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡调违,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年窟哺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片技肩。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖浮声,靈堂內的尸體忽然破棺而出虚婿,到底是詐尸還是另有隱情,我是刑警寧澤泳挥,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布然痊,位于F島的核電站,受9級特大地震影響屉符,放射性物質發(fā)生泄漏剧浸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一矗钟、第九天 我趴在偏房一處隱蔽的房頂上張望唆香。 院中可真熱鬧,春花似錦吨艇、人聲如沸躬它。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯吓。三九已至,卻和暖如春疮跑,著一層夾襖步出監(jiān)牢的瞬間组贺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工祖娘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留失尖,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像雹仿,于是被迫代替她去往敵國和親增热。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內容