iOS - AutoreleasePool - 底层篇

[toc]

参考

AutoreleasePool 底层

私有函数打印

可以通过以下私有函数来查看自动释放池的情况:

// extern 声明系统内部的函数
extern void _objc_autoreleasePoolPrint(void);
示例1
extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool { //  r1 = push()
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        @autoreleasepool { // r2 = push()
            Person *p3 = [[[Person alloc] init] autorelease];
            @autoreleasepool { // r3 = push()
                Person *p4 = [[[Person alloc] init] autorelease]; 
                _objc_autoreleasePoolPrint(); // 调用该私有函数
            } // pop(r3)
            
        } // pop(r2)
    } // pop(r1)
    return 0;
}

控制台输出如下:
objc[1405]: ##############
objc[1405]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[1405]: 7 releases pending.
objc[1405]: [0x10300d000]  ................  PAGE  (hot) (cold)
objc[1405]: [0x10300d038]  ################  POOL 0x10300d038 // Boundary
objc[1405]: [0x10300d040]       0x1007adcd0  Person
objc[1405]: [0x10300d048]       0x1007acd90  Person
objc[1405]: [0x10300d050]  ################  POOL 0x10300d050
objc[1405]: [0x10300d058]       0x1007ab730  Person
objc[1405]: [0x10300d060]  ################  POOL 0x10300d060
objc[1405]: [0x10300d068]       0x1007acc50  Person
objc[1405]: ##############
示例2

一页不够的情况

int main(int argc, const char * argv[]) {
    @autoreleasepool { //  r1 = push()
        Person *p1 = [[[Person alloc] init] autorelease];
        Person *p2 = [[[Person alloc] init] autorelease];
        @autoreleasepool { // r2 = push()
            for (int i = 0; i < 600; i++) { // 这里 autorelease 了 600 个对象
                Person *p3 = [[[Person alloc] init] autorelease];
            }
            @autoreleasepool { // r3 = push()
                Person *p4 = [[[Person alloc] init] autorelease];
                _objc_autoreleasePoolPrint();
            } // pop(r3)
        } // pop(r2)
    } // pop(r1)
    return 0;
}

控制台输出如下:
objc[1461]: ##############
objc[1461]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[1461]: 606 releases pending.
objc[1461]: [0x10300d000]  ................  PAGE (full)  (cold)
objc[1461]: [0x10300d038]  ################  POOL 0x10300d038
objc[1461]: [0x10300d040]       0x1005addd0  Person
objc[1461]: [0x10300d048]       0x1005ace90  Person
objc[1461]: [0x10300d050]  ################  POOL 0x10300d050
objc[1461]: [0x10300d058]       0x1005ab830  Person
.
.
.
objc[1461]: [0x10300dff8]       0x1005b5d50  Person
objc[1461]: [0x10300a000]  ................  PAGE  (hot) // 当前page; 上一页容量不够, 换页了
objc[1461]: [0x10300a038]       0x1005b5d60  Person
.
.
.
objc[1461]: [0x10300a348]       0x1005b6380  Person
objc[1461]: [0x10300a350]  ################  POOL 0x10300a350
objc[1461]: [0x10300a358]       0x1005b6390  Person
objc[1461]: ##############
Program ended with exit code: 0

源码分析:

  • objc4源码 NSObject.mm
  • clang重写 @autoreleasepool{}

objc4源码

https://opensource.apple.com/tarballs/objc4/

clang重写

OC代码:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}
转成C++
int main(int argc, const char * argv[]) {
    {
        __AtAutoreleasePool __autoreleasepool;
        // 为简洁, 这里精简了string
        NSLog((NSString *)&__NSConstantStringImpl__var_xxxxxx_mi_0); 
    }
    return 0;
}

可以看到: @autoreleasepool {} 在编译时被转换为一个 __AtAutoreleasePool 类型的局部变量 __autoreleasepool ; ★

__AtAutoreleasePool

结构

__AtAutoreleasePool 是个结构体, 定义如下: (定义在.cpp上部)

struct __AtAutoreleasePool {
    // __AtAutoreleasePool的构造函数
    __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } 
    // __AtAutoreleasePool的析构函数
    ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } 
    // POOL_BOUNDARY 地址, push的返回值; pop的入参
    void * atautoreleasepoolobj;
};
实现原理

可以看到, __AtAutoreleasePool 这个结构体会在:

  • 初始化时, 调用 objc_autoreleasePoolPush() 方法;
  • 在析构时, 调用 objc_autoreleasePoolPop() 方法;

根据C++ 构造函数析构函数 的特点:

  • 自动局部变量的 构造函数, 是在程序执行到声明这个对象的位置时调用的;
  • 自动局部变量的 析构函数, 是在程序执行到离开这个对象的作用域时调用;

所以:

  • 声明 __autoreleasepool 变量时, 构造函数 __AtAutoreleasePool() 被调用;

    即执行: atautoreleasepoolobj = objc_autoreleasePoolPush();

  • 出当前作用域时, 析构函数 ~__AtAutoreleasePool() 被调用;

    即执行: objc_autoreleasePoolPop(atautoreleasepoolobj);

即, 在 __autoreleasepool 初始化和析构时, 分别调用 objc_autoreleasePoolPush()objc_autoreleasePoolPop() ; ★

所以可以认为,

当我们使用 @autoreleasepool{} 时, 编译器实际上将其转化为以下代码:

int main(int argc, const char * argv[]) {
    {
        // 即将进入runloop, 调用push(), 创建pool, 
        void *atautoreleasepoolobj = objc_autoreleasePoolPush(); 
        
        NSLog((NSString *)&__NSConstantStringImpl__var_xxxxxx_mi_0);
        
        // 即将退出当前runloop时, 调用pop(), 释放pool
        objc_autoreleasePoolPop(atautoreleasepoolobj); 
    }
    return 0;
}

可见, @autoreleasepool{} 只是帮助我们封装了 objc_autoreleasePoolPush()objc_autoreleasePoolPop() 这两行代码, 让代码看起来更简洁。★

至此, 我们可以分析出, 单个自动释放池的运行过程可简单概括为:

objc_autoreleasePoolPush() 
[object autorelease] ;
objc_autoreleasePoolPop(void *)

objc_autoreleasePoolPush()objc_autoreleasePoolPop() 这两个方法实际是对 AutoreleasePoolPage 对应的静态方法 push()pop() 的封装。

// NSObject.mm
void *objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::push();
}

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

push()pop() 见下节 AutoreleasePoolPage 分析


AutoreleasePoolPage ★

简介

AutoreleasePool 没有单独的结构, 而是由若干个 AutoreleasePoolPage双向链表的形式组合而成。 ★

每个 AutoreleasePoolPage 的容量是 4096 字节 (0x1000, 4k, 也就是虚拟内存每个扇区的大小, 4k对齐由此而来), 除了用来存放它内部的成员变量(56B),剩下的空间(4040B)用来存放autorelease对象的地址;

图示
image
结构
class AutoreleasePoolPage
// AutoreleasePoolPage 是一个 C++ 的类, 定义在 NSObject.mm 中
class AutoreleasePoolPage : private AutoreleasePoolPageData {
    // 空池占位
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)  
    
    // 边界对象, 老版本源码中变量名是 POOL_SENTINEL(哨兵对象), 用来区别每个 AutoreleasePoolPage 边界
#   define POOL_BOUNDARY nil 
    
    // 
    static pthread_key_t const key = AUTORELEASE_POOL_KEY; 
    // 0xA3A3A3A3 after releasing
    static uint8_t const SCRIBBLE = 0xA3;  
    // PAGE_MAX_SIZE = 4096, 每个 AutoreleasePoolPage 的大小
    static size_t const SIZE = PAGE_MAX_SIZE;  
    // 一个page里对象数
    static size_t const COUNT = SIZE / sizeof(id); 

    /// 下面这7个成员, 每个占8字节, 共56字节 ★
    
    magic_t const magic; // 用于对当前 AutoreleasePoolPage 进行完整性校验;
    
    // 指向最新添加的 autoreleased 对象的下一个位置, 初始化时指向 begin(); 
    // 指向下一个能够存放 autoreleased 对象地址的位置, 相当于一个游标
    id *next; 
    pthread_t const thread; // 指向当前线程;  AutoreleasePool 是按线程一一对应的 ★
    
    AutoreleasePoolPage * const parent; // 父结点, 指向前一个page, 第一个结点的 parent 值为 nil;
    AutoreleasePoolPage *child; // 子结点, 指向下一个page, 最后一个结点的 child 值为 nil;
    
    uint32_t const depth; // 链表的深度, 节点个数, 从 0 开始, 往后递增 1;
    uint32_t hiwat; // 代表 high water mark; 数据容纳的上限
    
    // ... 
};
struct AutoreleasePoolPageData
// class AutoreleasePoolPage : private AutoreleasePoolPageData
struct AutoreleasePoolPageData {
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};
双向链表

一个 AutoreleasePoolPage 的空间被占满时, 会新建一个 AutoreleasePoolPage 对象, 连接链表, 后来的 autorelease 对象在新的 page 加入。★

AutoreleasePoolPage 本质是结构体, 结构体中包含 parent 和 child 指针, parent 指向前一个 page, child 指向下一个 page, 从而构成双向链表。

AutoreleasePoolPage 的组织是个栈, 结构成员 id *next 作为游标, 指向栈顶下一个为空的内存地址。★

但他并不是指内存中的栈区;

AutoreleasePoolPage 是个对象, 内存在堆区;

begin()
// 起始地址
id * begin() {
    // 起始地址 + 自身大小
    return (id *) ((uint8_t *)this+sizeof(*this));
}
end()
// 结束地址
id * end() {
    // 起始地址 + 4096
    return (id *) ((uint8_t *)this+SIZE);
}

// 4096
static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
hotPage()
// 返回当前正在使用的page
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;
}
push() ★
// 将 POOL_BOUNDARY 压到 page 的内存中, 占8字节; 并返回其所在的内存地址
// 存在嵌套的 pool 时, 每来到一个`{`, 就会压入1个 POOL_BOUNDARY 到 page 中, 每个 pool 对应一个 POOL_BOUNDARY  ★
static inline void *push() {
    id *dest;
    // 一开始没有page, 创建新的 
    if (slowpath(DebugPoolAllocation)) {
        // Each autorelease pool starts on a new pool page.
        dest = autoreleaseNewPage(POOL_BOUNDARY); 
    } 
    // 本来就有page
    else {
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
autoreleaseNewPage()
// 创建新的 page; 入参 obj 为 POOL_BOUNDARY
static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {
    AutoreleasePoolPage *page = hotPage();
    if (page) return autoreleaseFullPage(obj, page);
    else return autoreleaseNoPage(obj);
}
autoreleaseFullPage()
// 热页满了
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); // 如果满了 & 无子page, 传入当前page, 创建新page
    } while (page->full());

    setHotPage(page);
    // 将 POOL_BOUNDARY 压入 page
    return page->add(obj);
}
AutoreleasePoolPage()
// 构造函数
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    AutoreleasePoolPageData(begin(),
                            objc_thread_self(),
                            newParent,
                            newParent ? 1+newParent->depth : 0,
                            newParent ? newParent->hiwat : 0)
{ 
    if (parent) {
        parent->check();
        ASSERT(!parent->child);
        parent->unprotect();
        parent->child = this;
        parent->protect();
    }
    protect();
}
autoreleaseFast()
// 本来就有page, 入参 obj 为 POOL_BOUNDARY 或者 autoreleased 对象
static inline id *autoreleaseFast(id obj) {
    AutoreleasePoolPage *page = hotPage();
    
    if (page && !page->full()) { // 有热页, 没满
        // 添加到page
        return page->add(obj); 
    } else if (page) { // 有热页, 满了
        return autoreleaseFullPage(obj, page);
    } else { // 没有热页, 创建新页
        return autoreleaseNoPage(obj);
    }
}
autoreleaseNoPage()
// 创建新页; 入参为 POOL_BOUNDARY
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  &&  DebugMissingPools) {
        // We are pushing an object with no pool in place, 
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     objc_thread_self(), (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    }
    else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
        // 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);
}
pop() ★
// 传入之前 push() 所返回的 POOL_BOUNDARY 地址
// 会从最后一个压入 page 的对象开始(栈顶), 依次调用其 release 方法, 直到这个 POOL_BOUNDARY
// 存在嵌套pool时, 每离开一个 `}` 就会pop() 一次, 然后清理掉对应的 `{` 之后的autoreleased对象, 以及该 POOL_BOUNDARY
static inline void pop(void *token) {
    AutoreleasePoolPage *page;
    id *stop;
    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        // Popping the top-level placeholder pool.
        page = hotPage();
        if (!page) {
            // Pool was never used. Clear the placeholder.
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        page = coldPage();
        token = page->begin();
    } else {
        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);
        }
    }

    if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
        return popPageDebug(token, page, stop);
    }

    return popPage<false>(token, page, stop);
}
popPage()
static void popPage(void *token, AutoreleasePoolPage *page, id *stop) {
    if (allowDebug && PrintPoolHiwat) printHiwat();

    page->releaseUntil(stop);

    // memory: delete empty children
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top)
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } else 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();
        }
    }
}
releaseUntil()
// 迭代释放对象, 直到stop
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);
        }

        page->unprotect();
        id obj = *--page->next;
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();
        // 只要不是 POOL_BOUNDARY 就 release
        if (obj != POOL_BOUNDARY) {
            objc_release(obj);
        }
    }

    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
        ASSERT(page->empty());
    }
#endif
}

autorelease ★
autorelease
+ (id)autorelease {
    return (id)self;
}

// Replaced by ObjectAlloc
- (id)autorelease {
    return _objc_rootAutorelease(self);
}
_objc_rootAutorelease()
NEVER_INLINE id _objc_rootAutorelease(id obj) {
    ASSERT(obj);
    return obj->rootAutorelease();
}
rootAutorelease()
// Base autorelease implementation, ignoring overrides.
inline id  objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}
rootAutorelease2()
__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    ASSERT(!isTaggedPointer());
    // 传入调用 autorelease 的对象
    return AutoreleasePoolPage::autorelease((id)this); 
}
autorelease() ☆
// AutoreleasePoolPage 的方法; 入参为 autoreleased 对象
static inline id autorelease(id obj) {
    ASSERT(obj);
    ASSERT(!obj->isTaggedPointer());
    // 调用 autoreleaseFast, 将 obj 压入page
    id *dest __unused = autoreleaseFast(obj);
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
    return obj;
}
POOL_BOUNDARY

push()时:

autoreleasePool 初始化, 调用 objc_autoreleasePoolPush() ;

每次push都会创建一个新的 pool, 在 AutoreleasePoolPage 中的 next 指针指向的地址插入一个 边界对象, 然后返回这个边界对象的地址, 用于pop()时传入 ;

将 next 指针重新指向栈顶下一个为空的内存地址; (此时应为边界对象上面的地址) ;

pop()时:

autoreleasePool 析构, 调用 objc_autoreleasePoolPop() ;

会销毁当前的 pool, 会向pool中晚于传入的边界对象插入的所有对象都发送一次 release 消息, 直到传入的边界对象; 从最后插入到pool的对象一直向前清理, 可以向前跨越若干个page, 直到边界对象所在的page;

将 next 指针重新指向栈顶下一个为空的内存地址; (此时应为被释放的边界对象之前所处的地址)

autorelease()时:

在 AutoreleasePoolPage 中的 next指针指向的地址插入 需要加入 autoreleasepool 的对象;

将next指针重新指向栈顶下一个为空的内存地址; (此时应为刚刚入栈对象上面的地址)

总结

自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342