alloc的摸索

当创建一个对象时,我们常用的是[[Person alloc] init]或者[Person new]

Person *p1 = [Person alloc];
Person *p2 = [p1 init];
Person *p3 = [p1 init];
// 其中p1、p2、p3所指向的地址都是一样的
// 但是&p1、&p2、&p3是三个不一样的对象,只是指向了同一个内存空间

由上,我们知道了当调用alloc的时候,就创建了内存对象。
alloc如何实现的呢?而init是何作用呢?
与之对应的[Person new]又是怎么实现的?

通过三种方式了解所调用的内部函数libobjc.A.dylibobjc_alloc:

  1. 直接下断点,通过control + in进入汇编内容
  2. 通过「Symbolic Breakpoint」,alloc符号断点到对应的NSObject创建方法
  3. 通过「Debug Workflow - Always Show Disassembly」汇编指令进行跟踪

源码了解

既然了解大致的对象创建,但是要真正的了解alloc原理、流程还相应的源码
通过源码的调用顺序,大概整理了一下alloc的执行流程。

alloc流程

alloc流程

虽然梳理了流程,但是是否真的就如所梳理的流程那也执行呢?

为了能够进行代码断点跟踪,就需要进行源码的编译运行。
Cooci【iOS_objc4-756.2 最新源码编译调试】

通过源码项目分析,我们可以清晰的跟踪函数调用顺序。

Person *p = [Person alloc];

调用到的是

+ (id)alloc {
    return _objc_rootAlloc(self);
}

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id _objc_rootAlloc(Class cls) {
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) {

    if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        // No alloc/allocWithZone implementation. Go straight to the allocator.
        // fixme store hasCustomAWZ in the non-meta class and 
        // add it to canAllocFast's summary
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    // No shortcuts available.
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

对于callAlloc方法中的代码,先了解一下slowpathfastpath

// x 很可能不为 0,希望编译器进行优化
#define fastpath(x) (__builtin_expect(bool(x), 1))
// x 很可能为 0,希望编译器进行优化
#define slowpath(x) (__builtin_expect(bool(x), 0))

// (checkNil && !cls)大概率是false,因此不用事先加载[return nil]这一代码
if (slowpath(checkNil && !cls)) return nil;

接下来的,继续代码的执行

// 如果该类实现了allocWithZone方法,那么就不会走if里的逻辑,直接跳过
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
    // No alloc/allocWithZone implementation. Go straight to the allocator.
    // fixme store hasCustomAWZ in the non-meta class and 
    // add it to canAllocFast's summary
    // 是否快速创建判断,内部调用了bits.canAllocFast()默认为false
    if (fastpath(cls->canAllocFast())) {
    } else {
    }
}
创建实例的方法class_createInstance函数

正常流程下来,如果没有重写allocWithZone不是快捷alloc会到id obj = class_createInstance(cls, 0);这句代码中,即下面代码。

id class_createInstance(Class cls, size_t extraBytes) {
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil) {

    if (!cls) return nil;

    assert(cls->isRealized());

    // Read class's info bits all at once for performance
    //类构造函数和析构函数
    bool hasCxxCtor = cls->hasCxxCtor();
    bool hasCxxDtor = cls->hasCxxDtor();
    // (!instancesRequireRawIsa()); 实例需不要原始Isa
    bool fast = cls->canAllocNonpointer();
    //根据内存对齐原则,最少分配16字节的内存
    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;

    id obj;
    if (!zone  &&  fast) { //zone == nil && fast == YES
        obj = (id)calloc(1, size);  //根据size创建内存空间
        if (!obj) return nil;
        obj->initInstanceIsa(cls, hasCxxDtor);  // isa初始化(关联isa)
    } 
    else {
        if (zone) { // 有额外的字节要求
            obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
        } else {  // 没有额外的字节要求
            obj = (id)calloc(1, size);
        }
        if (!obj) return nil;

        // Use raw pointer isa on the assumption that they might be 
        // doing something weird with the zone or RR.
        obj->initIsa(cls);
    }
    if (cxxConstruct && hasCxxCtor) {
        obj = _objc_constructOrFree(obj, cls);
    }
    return obj;
}

如果重写了allocWithZoneallocWithZone=true,则执行[cls allocWithZone:nil];,最后还是来到class_createInstance()

// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {
    return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}

id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) {

    id obj;
#if __OBJC2__
    // allocWithZone under __OBJC2__ ignores the zone parameter
    (void)zone;
    obj = class_createInstance(cls, 0);
#else
    if (!zone) {
        obj = class_createInstance(cls, 0);
    }
    else {
        obj = class_createInstanceFromZone(cls, 0, zone);
    }
#endif
    if (slowpath(!obj)) obj = callBadAllocHandler(cls);
    return obj;
}

至此,一个完整的alloc流程基本已经走完,返回了对应的obj
那回到上面的问题中,init又是做了什么?new又是怎么实现创建的呢?

// Replaced by CF (throws an NSException)
+ (id)init {
    return (id)self;
}

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj) {
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}
+ (id)new {
    return [callAlloc(self, false/*checkNil*/) init];
}

通过代码我们很清楚的就能知道,
init没有任何实现,只是简单的返回本身而已,由此可以推断是预留给iOSer做工厂模式。
new只是换一种方式去调用alloc而已。

alloc()创建对象的过程梳理到最后就是calloc()创建内存空间,initIsa()进行isa的Class关联。
那么我们就要转移视线,去了解一下calloc()initIsa()都做了什么?

calloc申请内存

创建对象,我们需要开辟多少的内存空间?
其中指针,对象,占用8字节,结构体则是按照结构中的详细内容进行内存计算。

    size_t size = cls->instanceSize(extraBytes);
    if (outAllocatedSize) *outAllocatedSize = size;
#ifdef __LP64__
#   define WORD_SHIFT 3UL
#   define WORD_MASK 7UL
#   define WORD_BITS 64
#else
#   define WORD_SHIFT 2UL
#   define WORD_MASK 3UL
#   define WORD_BITS 32
#endif

// 进行与或非的操作,进行字节对齐
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}


// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
    assert(isRealized());
    return data()->ro->instanceSize;  // 遍历其中的属性,变量等实例的空间大小
}

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
    return word_align(unalignedInstanceSize());
}

size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.(不小于16byte)
    if (size < 16) size = 16;
    return size;
}
内存空间分布
对象内存空间分布
结构体内存大小

每一个属性会根据自身大小占用相应的内存空间,并且一次排列,不足位数的进行补齐,但是为何同样的结构体内容,却因为内容顺序不同,占用不同的内存空间?
那就让我们继续~

内存对齐规则
  • 数据成员对齐规则:结构体(struct)或联合体(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的其实位置要从该成员大小或者成员的子成员大小(只要该成员又子成员,比如数组,结构体等)的整数倍开始(比如int为4字节,则要从4的整数倍地址开始存储)。
  • 结构体作为成员:一个结构体中又另外的结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储(struct a中存在struct b,b中又char,int,double元素,则struct b应该从8的整数倍开始存储)。
  • 收尾工作:结构体的总大小,也就是sizeof的结果,必须是内部最大成员的整数倍,不足的要补齐。
结构体内存对齐

为了验证对象的属性的内存对齐规则,对上面的Person类新增加几个属性。

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@property (nonatomic, strong) NSString *hobby;

@property (nonatomic) char char1;
@property (nonatomic) char char2;

@end
对象内存对齐

从实例中的内存开辟情况可以发现,在申请内存的同时,还做了相应的顺序调整,使得申请的内存大小得到了优化。「二进制重排」

instanceSize 和 malloc_size
截屏2019-12-3016.14.55.png

从上面的输入,发现同一个对象,为何输出的size却是不一样的?
为此,我们需要先根据内存对齐的原则,进行内存空间大小的一个计算

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;    //8byte
@property (nonatomic, assign) int age;         //4byte
@property (nonatomic, assign) long height;     //8byte
@property (nonatomic, strong) NSString *hobby; //8byte

@property (nonatomic) char char1;              //1byte
@property (nonatomic) char char2;              //1byte

@end

通过计算 8(name) + 8(height) + 8(hobby) + (4(age) + 1(char1) + 1(char2) + 2(补齐)) = 32byte + 8byte(isa) = 40byte

通过函数名称可以推测:
instanceSize是对象所需要申请的内存大小,
malloc_size是系统实际开辟的内存大小,并且它们是不一致的。

为了证实这一推测,就需要去了解一下calloc是如何创建内存空间的。

void *
calloc(size_t num_items, size_t size) {
    void *retval;
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}

void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) {

    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_START, (uintptr_t)zone, num_items, size, 0);
    void *ptr;
    if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
        internal_check();
    }
    // 返回的指针,其中应该创建好了内存空间大小
    ptr = zone->calloc(zone, num_items, size);
    
    if (malloc_logger) {
        malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
                (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
    }

    MALLOC_TRACE(TRACE_calloc | DBG_FUNC_END, (uintptr_t)zone, num_items, size, (uintptr_t)ptr);
    return ptr;
}

其中ptr = zone->calloc(zone, num_items, size);调用的属性函数。因此我们可以打印一下其中的实现

ptr = zone->calloc(zone, num_items, size);

static void *
default_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size) {
    zone = runtime_default_zone();
    return zone->calloc(zone, num_items, size);  //依旧是一个属性函数
}
zone->calloc(zone, num_items, size);
static void *
nano_malloc(nanozone_t *nanozone, size_t size) {

    if (size <= NANO_MAX_SIZE) {
        void *p = _nano_malloc_check_clear(nanozone, size, 0);
        if (p) {
            return p;
        } else {
            /* FALLTHROUGH to helper zone */
        }
    }
    malloc_zone_t *zone = (malloc_zone_t *)(nanozone->helper_zone);
    return zone->malloc(zone, size);
}
static void *
_nano_malloc_check_clear(nanozone_t *nanozone, size_t size, boolean_t cleared_requested) {

    MALLOC_TRACE(TRACE_nano_malloc, (uintptr_t)nanozone, size, cleared_requested, 0);
    void *ptr;
    size_t slot_key;
    size_t slot_bytes = segregated_size_to_fit(nanozone, size, &slot_key); // Note slot_key is set here
    mag_index_t mag_index = nano_mag_index(nanozone);

    nano_meta_admin_t pMeta = &(nanozone->meta_data[mag_index][slot_key]);

    ptr = OSAtomicDequeue(&(pMeta->slot_LIFO), offsetof(struct chained_block_s, next));
    if (ptr) {
        // 略过(主要是Error处理)
    } else {
        ptr = segregated_next_block(nanozone, pMeta, slot_bytes, mag_index);
    }

    if (cleared_requested && ptr) {
        memset(ptr, 0, slot_bytes); // TODO: Needs a memory barrier after memset to ensure zeroes land first?
    }
    return ptr;
}

_nano_malloc_check_clear中,发现有一个slot_bytes变量,代替了外层传入的size,因此我们需要了解一下segregated_size_to_fit做了什么?

#define SHIFT_NANO_QUANTUM      4
#define NANO_REGIME_QUANTA_SIZE (1 << SHIFT_NANO_QUANTUM)

static MALLOC_INLINE size_t
segregated_size_to_fit(nanozone_t *nanozone, size_t size, size_t *pKey) {
    size_t k, slot_bytes;

    if (0 == size) {
        size = NANO_REGIME_QUANTA_SIZE; // Historical behavior
    }
    k = (size + NANO_REGIME_QUANTA_SIZE - 1) >> SHIFT_NANO_QUANTUM; // round up and shift for number of quanta
    slot_bytes = k << SHIFT_NANO_QUANTUM;                           // multiply by power of two quanta size
    *pKey = k - 1;                                                  // Zero-based!

    return slot_bytes;
}

追踪到segregated_size_to_fit,发现其中做的操作,便是对size_t size进行了16字节对齐操作,因此当需要是size_t size为40byte时,calloc最终创建的空间大小便是48byte(以整个对象为整体的进行字节对齐)。
因此就能说明为什么instanceSize所需要的内存大小是40byte,而系统malloc_size创建出来的实际大小却是48byte。

calloc()创建完内存空间后,接下来就是isa的关联了!

isa

首先先了解一下isa是什么?

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

通过源码,发现是一个union联合体。
包含了两个初始化,一个Class cls,一个uintptr_t bits,和一个结构体,其中属性clsbits是两个互斥的,仅需对其一进行赋值。

当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union),利用union可以用相同的存储空间存储不同型别的数据类型,从而节省内存空间。
而为了解决union数据读写麻烦,所以有了位域的概念,也就是isa_t联合体中的结构体。

下面就是isa_t里面定义的位域-ISA_BITFIELD

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)

# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

通过宏定义,x86_64和arm64下的位域定义存在差异,不过都是占用了64bit。

含义:(以arm64为模版)

  • nonpointer:1bit
    表示是否对isa开启指针优化。0代表纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。

  • has_assoc:1bit
    关联对象标志位,0没有,1存在。

  • has_cxx_dtor:1bit
    该对象是否有C++或者Objc的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。

  • shiftcls:33bit
    存储类指针的值。开启指针优化的情况下,arm64架构中有33bit来存储类指针。

  • magic:6bit
    用于调试器判断当前对象是真的对象还是一段没有初始化的空间。

  • weakly_referenced:1bit
    标志对象释放被指向或者曾指向一个ARC的弱变量,没有弱引用的对象可以更快释放。

  • deallocating:1bit
    标志对象释放正在释放内存。

  • has_sidetable_rc:1bit
    当对象应用计数大于10时,则需要借用该变量存储进位。

  • extra_rc:19bit
    表示该对象的引用计数值,实际上是引用计数减一。例如:如果引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc

isa的初始化

inline void 
objc_object::initIsa(Class cls) {
    initIsa(cls, false, false);
}

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());
    initIsa(cls, true, hasCxxDtor);
}

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)  { 
    assert(!isTaggedPointer()); 
    
    if (!nonpointer) {  //
        isa.cls = cls;
    } else {  //对isa联合体位域进行初始化赋值
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());

        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.nonpointer is part of ISA_MAGIC_VALUE
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        // This write must be performed in a single store in some cases
        // (for example when realizing a class because other threads
        // may simultaneously try to use the class).
        // fixme use atomics here to guarantee single-store and to
        // guarantee memory order w.r.t. the class index table
        // ...but not too atomic because we don't want to hurt instantiation
        isa = newisa;
    }
}

既然创建了对象,isa和对象的关联在哪?

进行对象内存的打印,我们要知道对象的第一个属性必然是isa,那就需要验证一下。
结合代码和联合体位域,我们知道isa.shiftcls是存储类指针的值。
其中shiftclsarm64中包含了33bit,在__x86_64__中包含了44bit

在__x86_64__下的isa.shiftcls查找

通过位移过滤的方式,就能获取到对应到shiftcls信息,而在代码中,我们也能发现存在相应的函数替我们进行isa_mask

/***********************************************************************
* object_getClass.
* Locking: None. If you add locking, tell gdb (rdar://7516456).
**********************************************************************/
Class object_getClass(id obj) {
    if (obj) return obj->getIsa();
    else return Nil;
}

inline Class 
objc_object::getIsa()  {
    if (!isTaggedPointer()) return ISA();

    uintptr_t ptr = (uintptr_t)this;
    if (isExtTaggedPointer()) {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        return objc_tag_ext_classes[slot];
    } else {
        uintptr_t slot = 
            (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
        return objc_tag_classes[slot];
    }
}

inline Class 
objc_object::ISA()  {
    assert(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

通过object_getClass函数,到最后的(Class)(isa.bits & ISA_MASK);
在不同架构中,对应的位域是不同的!

isa.bits & ISA_MASK
isa的指向关系

至此,我们通过对象内存中找到了类信息的存在,那我们就能通过关联,去查找isa的指向关系。

isa的指向
类继承指向

ISA的指向:

  • instance对象的isa指向了class对象
  • class对象的isa指向了meta-class对象
  • meta-class对象的isa指向了基类的meta-class对象

的继承关系:

  • Subclass继承于SuperClass,meta_subClass继承于meta_superClass
  • SuperClass继承于RootClass,meta_superClass继承于meta_rootClass
  • meta_rootClass继承于RootClass
  • RootClass指向nil(NSObject的父类是nil)
isa、类

最后

对象alloc的创建,需要有calloc去开辟内存空间,需要isa进行类的关联。

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

推荐阅读更多精彩内容