iOS底層探索之類結構

一濒募、前置知識

CPU 訪問內(nèi)存時需要的是地址吭产,而不是變量名和函數(shù)名!變量名和函數(shù)名只是地址的一種助記符脉幢,當源文件被編譯和鏈接成可執(zhí)行程序后歪沃,它們都會被替換成地址嗦锐。編譯和鏈接過程的一項重要任務就是找到這些名稱所對應的地址。

1.1 C語言指針變量的運算(加法沪曙、減法和比較運算)

指針 變量保存的是 地址奕污,而地址本質(zhì)上是一個整數(shù),所以指針變量可以進行部分運算液走,例如加法碳默、減法、比較等缘眶,請看下面的代碼:

        int    a = 10,   *pa = &a, *paa = &a;
        double b = 99.9, *pb = &b;
        char   c = '@',  *pc = &c;
        //最初的值
        NSLog(@"最初的值:");
        NSLog(@"&a=%p, &b=%p, &c=%p \n", &a, &b, &c);
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //加法運算
        pa++; pb++; pc++;
        NSLog(@"各自+1后:");
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //減法運算
        NSLog(@"然后-2后:");
        pa -= 2; pb -= 2; pc -= 2;
        NSLog(@"pa=%p, pb=%p, pc=%p \n", pa, pb, pc);
        //比較運算
        if(pa == paa){
            NSLog(@"pa和paa相等: %d\n", *paa);
        }else{
            NSLog(@"pa和paa不相等: %d\n", *pa);
        }
最初的值:
&a=0x7ffeed1bbd80, &b=0x7ffeed1bbd78, &c=0x7ffeed1bbd87
pa=0x7ffeed1bbd80, pb=0x7ffeed1bbd78, pc=0x7ffeed1bbd87
各自+1后:
pa=0x7ffeed1bbd84, pb=0x7ffeed1bbd80, pc=0x7ffeed1bbd88
然后-2后:
pa=0x7ffeed1bbd7c, pb=0x7ffeed1bbd70, pc=0x7ffeed1bbd86
pa和paa不相等: 1079572889

從運算結果可以看出:pa腻窒、pbpc 每次加 1磅崭,它們的地址分別增加 4、8瓦哎、1砸喻,正好是 intdouble蒋譬、char 類型的長度割岛;減 2 時,地址分別減少 8犯助、16癣漆、2,正好是 int剂买、double惠爽、char 類型長度的 2 倍。

這很奇怪瞬哼,指針變量加減運算的結果跟數(shù)據(jù)類型的長度有關婚肆,而不是簡單地加 1 或減 1,這是為什么呢坐慰?

apa 為例,a 的類型為int,占用 4 個字節(jié)必盖,pa 是指向 a 的指針普监,如下圖所示:

c指針運算-0.jpg

剛開始的時候,pa 指向 a 的開頭糟港,通過 *pa 讀取數(shù)據(jù)時攀操,從 pa 指向的位置向后移動 4 個字節(jié),把這 4 個字節(jié)的內(nèi)容作為要獲取的數(shù)據(jù)秸抚,這 4 個字節(jié)也正好是變量 a 占用的內(nèi)存崔赌。

如果 pa++;使得地址加 1 的話意蛀,就會變成如下圖所示的指向關系:

c指針運算-1.jpg

這個時候 pa 指向整數(shù) a 的中間,*pa 使用的是紅色虛線畫出的 4 個字節(jié)健芭,其中前 3 個是變量 a 的县钥,后面 1 個是其它數(shù)據(jù)的,把它們“攪和”在一起顯然沒有實際的意義慈迈,取得的數(shù)據(jù)也會非常怪異若贮。

如果pa++ ;使得地址加 4 的話,正好能夠完全跳過整數(shù) a痒留,指向它后面的內(nèi)存谴麦,如下圖所示:

c指針運算-2.jpg

總結:指針做 + 、- 運算的時候伸头,是按指向內(nèi)容類型倍數(shù)運算匾效。

二、Class (進入正題)

上篇文章分析isa的時候恤磷,遇到了 Class面哼,Class 是什么,內(nèi)部又是怎么定義的扫步?

源碼用的還是objc4-787.1版本魔策。

2.1 源碼查看
typedef struct objc_class *Class;

繼續(xù)查看 objc_class,這個時候發(fā)現(xiàn)有3個 objc_class 的定義河胎,都是 struct闯袒,但是注意一個是 runtime.h 中的,會發(fā)現(xiàn) 已經(jīng)標記 OBJC2_UNAVAILABLE(OC2不可用了)游岳。一個是 objc-runtime-old.h (舊的不管)中的政敢,還有個 objc-runtime-new.h 中的,跟進 objc-runtime-new.h 中的代碼:

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() const {
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
  // 中間省略很多方法 ...
    bool isMetaClass() {
        ASSERT(this);
        ASSERT(isRealized());
#if FAST_CACHE_META
        return cache.getBit(FAST_CACHE_META);
#else
        return data()->flags & RW_META;
#endif
    }

    // Like isMetaClass, but also valid on un-realized classes
    bool isMetaClassMaybeUnrealized() {
        static_assert(offsetof(class_rw_t, flags) == offsetof(class_ro_t, flags), "flags alias");
        static_assert(RO_META == RW_META, "flags alias");
        return data()->flags & RW_META;
    }

    // NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

    bool isRootClass() {
        return superclass == nil;
    }
    bool isRootMetaclass() {
        return ISA() == (Class)this;
    }
    // 省略 ...
    
    // 以前分析alloc的時候胚迫,熟悉的instanceSize
    uint32_t alignedInstanceStart() const {
        return word_align(unalignedInstanceStart());
    }

    // May be unaligned depending on class's ivars.
    uint32_t unalignedInstanceSize() const {
        ASSERT(isRealized());
        return data()->ro()->instanceSize;
    }

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

    size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }

        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

    void setInstanceSize(uint32_t newSize) {
        ASSERT(isRealized());
        ASSERT(data()->flags & RW_REALIZING);
        auto ro = data()->ro();
        if (newSize != ro->instanceSize) {
            ASSERT(data()->flags & RW_COPIED_RO);
            *const_cast<uint32_t *>(&ro->instanceSize) = newSize;
        }
        cache.setFastInstanceSize(newSize);
    }

}

發(fā)現(xiàn) objc_class 繼承與 objc_object堕仔,繼續(xù)跟進 objc_object

struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    
    uintptr_t isaBits() const;

    // 省略很多方法 方法 方法 ...
}

發(fā)現(xiàn) objc_object 只有一個私有屬性 isa_t isa;

2.2 驗證經(jīng)典isa流程圖
isa流程圖.png

這個isa流程圖,見過好多次了晌区,明白其流程摩骨,但是不知其怎么來的,

在分析 alloc 的時候朗若,會初始化 instanceisa 賦值該對象的 Class恼五。

根據(jù)源碼可知:

  • 只要繼承與 NSObject 的對象都會有一個 isa
  • isa 中最重要的就是 shiftcls,其實就是指向 Class
        GLPerson *supP = [[GLPerson alloc] init];
        // GLStudent 繼承于 GLPerson
        GLStudent *subS = [[GLStudent alloc] init];
isa走位打印.png

驗證 subS 也是一樣的流程哭懈。

2.3 類(對象)只存在一份
        Class c1 = object_getClass(subS1);
        Class c2 = object_getClass(subS2);
        Class c3 = object_getClass(subS3);
        Class c4 = object_getClass(subS4);
        
        NSLog(@"c1:%p; c2:%p; c3:%p; c4:%p", c1, c2, c3, c4);

console: c1:0x1026ca820; c2:0x1026ca820; c3:0x1026ca820; c4:0x1026ca820

三灾馒、objc_class

對象初始化的時候,好像沒有要初始化屬性遣总、方法吧睬罗,那屬性轨功、方法、協(xié)議這些東西是放在那里了容达。

找了下 objc-runtime-old.h舊定義文件) 里面的定義古涧,發(fā)現(xiàn) objc_class 里面直接是有 ivars methodLists protocols 這些信息的。

而在 objc-runtime-new.h新定義文件)里面這些屬性沒了花盐,而多了一個 class_data_bits_t bits 屬性羡滑。

class_data_bits_t 源碼:

struct class_data_bits_t {
    friend objc_class;
    // Values are the FAST_ flags above.
    uintptr_t bits;

public:
    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
// 省略一些方法 ...
}

class_data_bits_t 里面有個方法 class_rw_t* data(),會返回 class_rw_t 的數(shù)據(jù)算芯。

繼續(xù)查看 class_rw_t

struct class_rw_t {
    // 省略 ...
    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
}

可在里面找到 method_array_t柒昏、property_array_tprotocol_array_t 這些方法熙揍。從字面意思應該能看出這就是我們要找的東西了职祷。

3.1 獲取 class_data_bits_t

運行環(huán)境是在配置好可運行的 objc 源碼的工程。

struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    // 省略...
}

通過 objc_class 的源碼可知届囚,bits 存在第4個成員有梆。只要計算出前面3個成員的大小,就可以通過內(nèi)存偏移得到 bits奖亚。isa 根據(jù)以前文章的分析可知是8字節(jié)。Class superclass 是一個指針類型析砸,所以也是8字節(jié)昔字。看下 cache_t 源碼:

struct cache_t {
// typedef uint32_t mask_t;
// typedef unsigned int uint32_t; 所以mask_t類型是4字節(jié)
// typedef unsigned long           uintptr_t; 所以 uintptr_t 類型是8字節(jié)
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 指針8字節(jié)
    explicit_atomic<mask_t> _mask;  // 4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字節(jié)
    mask_t _mask_unused; // 4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets; // long 8字節(jié)
    mask_t _mask_unused; // 4字節(jié)
#else
#error Unknown cache mask storage type.
#endif

// typedef unsigned short uint16_t; 這是uint16_t的定義
#if __LP64__
    uint16_t _flags;  // 2字節(jié)
#endif
    uint16_t _occupied; // 2字節(jié)

// 省略很多方法和靜態(tài)變量...
}

cache_t 源碼總結:不管第一個if else走哪個首繁,加上下面2個變量作郭,總共占用16字節(jié)。

計算偏移量

  • 繼承的isa : 8字節(jié)弦疮;
  • Class superclass : 8字節(jié)夹攒;
  • cache_t cache:16字節(jié);

所以胁塞,要想獲取 bits咏尝,總共需要在類的首地址上偏移32字節(jié)。

(lldb) x/4gx GLPerson.class
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 在GLPerson類首地址0x100002620的基礎上偏移32字節(jié)啸罢。記得是16進制哦
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
3.2 打印 class_rw_t 中存儲
@protocol GLPersonWalkProtocol <NSObject>
- (void)walk;
@end

@interface GLPerson : NSObject <GLPersonWalkProtocol>

@property (nonatomic, strong) NSString *name; /**<  8個字節(jié)  */
@property (nonatomic, strong) NSString *nickName; /**<  8個字節(jié)  */

- (void)sayHello;
+ (void)goSchool;

@end

@interface GLStudent : GLPerson
@end
---
        GLPerson *supP = [[GLPerson alloc] init];
        GLStudent *subS = [[GLStudent alloc] init];

通過 x 编检、ppo 命令打印

lldb) x/4gx supP
0x1006a4730: 0x001d800100002625 0x0000000000000000
0x1006a4740: 0x0000000000000000 0x0000000000000000
(lldb) po 0x001d800100002625 & 0x00007ffffffffff8ULL
GLPerson

(lldb) p 0x001d800100002625 & 0x00007ffffffffff8ULL
(unsigned long long) $2 = 4294977056 // 這是GLPerson類
(lldb) x/4gx $2  // 打印GLPerson類的地址和內(nèi)存
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 另一種方式打印GLPerson類的地址和內(nèi)存扰才,再次驗證是一樣的
(lldb) x/4gx GLPerson.class  
0x100002620: 0x00000001000025f8 0x0000000100334140
0x100002630: 0x00000001006a8540 0x0001802400000003
// 通過類的首地址偏移32字節(jié)得到$4允懂,$4即是class_data_bits_t的首地址
(lldb) p (class_data_bits_t *)0x100002640
(class_data_bits_t *) $4 = 0x0000000100002640
// 調(diào)用class_data_bits_t的方法的到class_rw_t數(shù)據(jù)
(lldb) p *$4->data()
(class_rw_t) $5 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294976416
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}

總結流程:

  1. 打印 GLPerson.class 的首地址和內(nèi)存;
  2. 通過地址偏移獲取 class_data_bits_t *指針賦值給 $4 變量衩匣;
  3. 通過 *$4 取地址里面的值可得到 class_data_bits_t蕾总;
  4. 然后調(diào)用 data() 方法粥航,返回 class_rw_t 數(shù)據(jù);
  5. 通過 p 打印 class_rw_t;
3.3 獲取 method_array_t
(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975632
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}
(lldb) p $3.methods()
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000020d8
      arrayAndFlag = 4294975704
    }
  }
}
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020d8
(lldb) p *$5
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 6
    first = {
      name = "sayHello"
      types = 0x0000000100000f7c "v16@0:8"
      imp = 0x0000000100000d80 (`-[GLPerson sayHello])
    }
  }
}
(lldb) p $6.get(0)
(method_t) $7 = {
  name = "sayHello"
  types = 0x0000000100000f7c "v16@0:8"
  imp = 0x0000000100000d80 (`-[GLPerson sayHello])
}
(lldb) p $6.get(1)
(method_t) $8 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f7c "v16@0:8"
  imp = 0x0000000100000e30 (`-[GLPerson .cxx_destruct])
}
(lldb) p $6.get(2)
(method_t) $9 = {
  name = "name"
  types = 0x0000000100000f90 "@16@0:8"
  imp = 0x0000000100000d90 (`-[GLPerson name])
}
(lldb) p $6.get(3)
(method_t) $10 = {
  name = "setName:"
  types = 0x0000000100000f98 "v24@0:8@16"
  imp = 0x0000000100000db0 (`-[GLPerson setName:])
}
(lldb) p $6.get(4)
(method_t) $11 = {
  name = "setNickName:"
  types = 0x0000000100000f98 "v24@0:8@16"
  imp = 0x0000000100000e00 (`-[GLPerson setNickName:])
}
(lldb) p $6.get(5)
(method_t) $12 = {
  name = "nickName"
  types = 0x0000000100000f90 "@16@0:8"
  imp = 0x0000000100000de0 (`-[GLPerson nickName])
}
(lldb) p $6.get(6)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/學習/源碼/alloc流程分析/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

總結流程

  1. 通過 *$2->data() 獲取到 (class_rw_t) $3
  2. 調(diào)用 (class_rw_t) $3methods 方法獲取 (const method_array_t) $4;
  3. 調(diào)用 $4.list方法 獲取指向 method_list_t 的指針 (method_list_t *const) $5;
  4. p *$5$5 指向的存儲內(nèi)容生百,得到 (method_list_t) $6递雀;
  5. method_list_t 繼承于 entsize_list_tt, entsize_list_tt有個方法 Element& get(uint32_t i) ,可根據(jù)第一個item偏移地址獲取要取的item置侍。
  6. 通過 get(0)映之、get(1) ... 獲取方法列表知道越界。

可以發(fā)現(xiàn)方法列表里有我們定義的實例方法蜡坊,卻沒有類方法 + (void)goSchool; 杠输。

3.4 獲取 property_array_t

同樣方式也可打印出 const property_array_t properties()

(lldb) x/4gx GLPerson.class
0x1000022b8: 0x0000000100002290 0x0000000100333140
0x1000022c8: 0x0000000100709790 0x0001802400000003
(lldb) p (class_data_bits_t *)0x1000022d8
(class_data_bits_t *) $2 = 0x00000001000022d8
(lldb) p *$2->data()
(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975632
  }
  firstSubclass = GLStudent
  nextSiblingClass = NSUUID
}
(lldb) p $3.properties()
(const property_array_t) $13 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000021b8
      arrayAndFlag = 4294975928
    }
  }
}
(lldb) p $13.list
(property_list_t *const) $14 = 0x00000001000021b8
(lldb) p *$14
(property_list_t) $15 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 2
    first = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
  }
}
(lldb) p $15.get(0)
(property_t) $16 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $15.get(1)
(property_t) $17 = (name = "nickName", attributes = "T@\"NSString\",&,N,V_nickName")
(lldb) p $15.get(2)
Assertion failed: (i < count), function get, file /Users/xulong/Desktop/學習/源碼/alloc流程分析/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.

繼續(xù)探索:類結構下篇


參考

C語言指針:http://c.biancheng.net/view/1992.html

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秕衙,一起剝皮案震驚了整個濱河市蠢甲,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌据忘,老刑警劉巖鹦牛,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異勇吊,居然都是意外死亡曼追,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門汉规,熙熙樓的掌柜王于貴愁眉苦臉地迎上來礼殊,“玉大人,你說我怎么就攤上這事针史【祝” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵啄枕,是天一觀的道長婚陪。 經(jīng)常有香客問我,道長频祝,這世上最難降的妖魔是什么泌参? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮常空,結果婚禮上及舍,老公的妹妹穿的比我還像新娘。我一直安慰自己窟绷,他們只是感情好锯玛,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般攘残。 火紅的嫁衣襯著肌膚如雪拙友。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天歼郭,我揣著相機與錄音遗契,去河邊找鬼。 笑死病曾,一個胖子當著我的面吹牛牍蜂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泰涂,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鲫竞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了逼蒙?” 一聲冷哼從身側響起从绘,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎是牢,沒想到半個月后僵井,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡驳棱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年批什,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片社搅。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡驻债,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罚渐,到底是詐尸還是另有隱情却汉,我是刑警寧澤驯妄,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布荷并,位于F島的核電站,受9級特大地震影響青扔,放射性物質(zhì)發(fā)生泄漏源织。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一微猖、第九天 我趴在偏房一處隱蔽的房頂上張望谈息。 院中可真熱鬧,春花似錦凛剥、人聲如沸侠仇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逻炊。三九已至互亮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間余素,已是汗流浹背豹休。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桨吊,地道東北人威根。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像视乐,于是被迫代替她去往敵國和親洛搀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359