关于孙源那道经典iOS面试题目的疑问

今天我们来说一下关于孙源之前提出的那道经典面试题.
题目如下:

@interface FJFPerson : NSObject
// name
@property (nonatomic, copy) NSString *name;
- (void)print;
@end

@implementation FJFPerson
- (void)print {
    NSLog(@"my name is %@", self.name);
}
@end

- (void)viewDidLoad {
    [super viewDidLoad];
    
    id cls = [FJFPerson class];
    void *obj = &cls;
    [(__bridge id)obj print];
}

打印出来的结果为:

2019-10-02 19:13:52.387769+0800 FJFRuntimeInterviewQuestionDemo[16143:509566] my name is <ViewController: 0x7ff0aac05df0>
打印结果.png

对于这个打印结果,我们先来说一下,之前比较官方的解释,然后我们再来说一下对于这个解释的疑问。

关于这个解释这篇文章也解释很清楚:

一道值得思考的iOS面试题

不同的是,我是通过汇编来解释堆栈关系,而我这边的重点是在第二部分,如果对于这个解释已经了解,可以直接看第二部分

一.结果的官方解释

A.为什么不会崩溃

  • id cls = [FJFPerson class];这句代码里面的cls指向的是FJFPerson这个类。

  • void *obj = &cls;然后在这里obj是一个指向cls的指针。

  • 而通过如下源码:

struct objc_object {
private:
    isa_t isa;
}
struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;
}

我们可以看出这里objc_object这个对象的首字段是isa指向一个Class

也就是说,这里的obj就相当于一个FJFPerson实例对象的指针,指向了cls,而cls就相当于isa指针,指向了FJFPerson类。

如下图所示:

对照图.png
  • [(__bridge id)obj print];所以这里调用就相当于FJFPerson实例对象的调用[person print],是能够正常调用

B.为什么打印出ViewController对象

    1. 我们在FJFPersonprint添加self&_name的地址信息
@implementation FJFPerson
- (void)print {
    NSLog(@"self: %p", self);
    NSLog(@"self.name: %p", &_name);
    
    NSLog(@"my name is %@", self.name);
}
@end

打印结果如下:

地址信息.png

从这个地址信息,我们可以看出selfname之间的地址差8个字节,即1个指针的距离.

  • 而在id cls = [Spark class];前面添加代码NSString *str = @"11111"; NSLog(@"cls address:%p str address:%p",&cls,&str);, 打印出如下信息:
地址对照图.png

可以看出:

  • cls的地址比str的地址值大8个字节。
  • self.namestr的地址值一样,指向字符串11111

因为函数调用采用栈的形式,栈的地址是从高地址到低地址,所以先入栈的strcls8个字节,而print函数里面的self地址和cls地址一致,是因为[obj print]的是通过clsisa来进行方法调用,所以self就是obj,而self.name的地址由于大self地址8个字节,所以self.name的地址刚好和str地址一致。

也就是说这里栈参数的数据结构格式,对应了obj对象地址的数据结构。

2. 至于为什么打印的是ViewController对象:

从上面分析我们可以看出self.name的值是在cls之前入栈的值,与cls相差8个字节,因此我们通过汇编分析下堆栈信息:

FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
    0x10f8b10d0 <+0>:   pushq  %rbp 
    0x10f8b10d1 <+1>:   movq   %rsp, %rbp 
    0x10f8b10d4 <+4>:   subq   $0x40, %rsp                - rsp - 0x40 ->开辟64个字节栈空间 
    0x10f8b10d8 <+8>:   movq   %rdi, -0x8(%rbp)           -  <ViewController: 0x7fd291512f90> 将self的值 给(rdp - 0x8) 
    0x10f8b10dc <+12>:  movq   %rsi, -0x10(%rbp)          -  rsi的 “viewDidLoad”, 将self的值给rdp - 0x10 
    0x10f8b10e0 <+16>:  movq   -0x8(%rbp), %rsi           -  将self的值给rsi 
    0x10f8b10e4 <+20>:  movq   %rsi, -0x20(%rbp)          - 将rsi的值给self给 内存地址(rdp-0x20) 
    0x10f8b10e8 <+24>:  movq   0x2e39(%rip), %rsi        ; (void *)0x000000010f8b3f40: ViewController 将”ViewController”字符串地址给rsi 
    0x10f8b10ef <+31>:  movq   %rsi, -0x18(%rbp)          - 将”ViewController”字符串的地址给(rbp - 0x18) 
    0x10f8b10f3 <+35>:  movq   0x2d7e(%rip), %rsi        ; “viewDidLoad" 将viewDidLoad的字符串地址给rsi 
    0x10f8b10fa <+42>:  leaq   -0x20(%rbp), %rdi          - 将内存地址(rbp-0x20)的地址给rdi 
    0x10f8b10fe <+46>:  callq  0x10f8b184e              ; symbol stub for: objc_msgSendSuper2 跳转到objc_msgSendSuper2命令 
    0x10f8b1103 <+51>:  movq   0x2de6(%rip), %rsi        ; (void *)0x000000010f8b4008: FJFPerson 将文本地址给rsi 
    0x10f8b110a <+58>:  movq   0x2d6f(%rip), %rdi        ; “class” 将"class”给rdi 
    0x10f8b1111 <+65>:  movq   %rdi, -0x38(%rbp)         - 将rdi(“class”)的值给(rbp-0x38) 
    0x10f8b1115 <+69>:  movq   %rsi, %rdi                - 将rsi(“FJFPerson”)给rdi 
    0x10f8b1118 <+72>:  movq   -0x38(%rbp), %rsi         - 将(rbp-0x38)的值给rsi 
    0x10f8b111c <+76>:  callq  *0x1ee6(%rip)             ; (void *)0x00007fff503b1780: objc_msgSend 调用objc_msgSend方法 
    0x10f8b1122 <+82>:  movq   %rax, %rdi                - 将返回值给rdi 
    0x10f8b1125 <+85>:  callq  0x10f8b185a              ; symbol stub for: objc_retainAutoreleasedReturnValue 
    0x10f8b112a <+90>:  movq   %rax, -0x28(%rbp)        - 将cls返回值给(rbp-0x28) 
->  0x10f8b112e <+94>:  leaq   -0x28(%rbp), %rax        将rbp-0x28地址给rax 
    0x10f8b1132 <+98>:  movq   %rax, -0x30(%rbp)        将rax的值(obj)给(rbp-0x30) 
    0x10f8b1136 <+102>: movq   -0x30(%rbp), %rdi         将(rbp-0x30)的值给rdi 
    0x10f8b113a <+106>: movq   0x2d47(%rip), %rsi        ; “print” 将print给rsi 
    0x10f8b1141 <+113>: callq  *0x1ec1(%rip)             ; (void *)0x00007fff503b1780: objc_msgSend 进行print函数调用 
当前栈.png

经过汇编的分析,我们很容易看出cls的之前入栈的是selfviewController本身。

以上就是官方给出的分析,我们总结一下:

所有NSObject对象的首地址都是指向这个对象的所属类,反过来说如果一个地址指向某个类,我们可以把这个地址当做对象去用。所以编译可以通过,进行方法调用也不会报错。

打印结果是ViewController对象的原因是因为cls在栈上的数据结构符合它作为真实类的数据结构,self.name的地址正好是self对象的地址.

二. 存在的疑问

大家都知道:

arm64架构之前,isa就是一个普通的指针,存储着classMeta-Class对象的内存地址。
arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。

isa.jpg

也就是说在arm64架构中,一个实例对象比如personisa指针并没有直接指向FJFPerson类,而是需要isa地址和相应架构的ISA_MASK掩码进行相与,才能得到真正的指向FJFPerson类的地址。

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL

接下来我们再来分析下调用的方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    id cls = [FJFPerson class];
    
    void *obj = &cls;
    [(__bridge id)obj print];

}
  • id cls = [FJFPerson class];这句代码里cls是指向FJFPerson类的指针
  • void *obj = &cls;这里的obj是一个二级指针,是指向cls的的指针,也就是说obj里面存储的就是一个单纯的cls地址值,并非是个共用体
  • [(__bridge id)obj print];这里是进行一个消息的发送

我们将ViewController.m转换为C++代码看一下:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController.cpp

我们可以看到如下C++代码:

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    id cls = ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FJFPerson"), sel_registerName("class"));

    void *obj = &cls;
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("print"));

}

我们可以看出这里是直接对obj进行print的消息发送。

接下来我们看下objc_msgSendarm64架构上的源码:

/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************/
    .data
    .align 3
    .globl _objc_debug_taggedpointer_classes
_objc_debug_taggedpointer_classes:
    .fill 16, 8, 0
    .globl _objc_debug_taggedpointer_ext_classes
_objc_debug_taggedpointer_ext_classes:
    .fill 256, 8, 0
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    MESSENGER_START
    cmp x0, #0          // nil check and tagged pointer check 检查x0即isa是否为nil或者tagged Pointer
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative) 如果为nil或者tagged Pointer 就跳转到 LNilOrTagged
    ldr x13, [x0]       // x13 = isa  将isa的值给x13
    and x16, x13, #ISA_MASK // x16 = class   将isa与isa_mask进行与操作得到相关的类地址
LGetIsaDone:
    CacheLookup NORMAL      // calls imp or objc_msgSend_uncached 进入正常的缓存方法查找
LNilOrTagged:
    b.eq    LReturnZero     // nil check nil的检测
    // tagged
    mov x10, #0xf000000000000000
    cmp x0, x10
    b.hs    LExtTag
    adrp    x10, _objc_debug_taggedpointer_classes@PAGE
    add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
    ubfx    x11, x0, #60, #4
    ldr x16, [x10, x11, LSL #3]
    b   LGetIsaDone

通过源码我们可以分析:

[(__bridge id)obj print];这句代码走到源码里面,源码取obj存储的地址,也就是cls的地址跟ISA_MASK进行相与,我们会发现,cls的地址与ISA_MASK相与后的结果是cls地址,我们加入一个FJFPerson的实例对象tmpPerson,发现tmpPersonisaISA_MASK相与也是真正的类FJFPerson地址,即cls的地址。

地址.png
(lldb) p/x cls
(id) $0 = 0x0000000104c98fe8
(lldb) p/x obj
(FJFPerson *) $1 = 0x000000016b16bd78
(lldb) p/x tmpPerson->isa
(Class) $2 = 0x000001a104c98fed FJFPerson
(lldb) p/x 0x000001a104c98fed & 0x0000000ffffffff8ULL
(unsigned long long) $3 = 0x0000000104c98fe8
(lldb) p/x 0x0000000104c98fe8 & 0x0000000ffffffff8ULL
(unsigned long long) $4 = 0x0000000104c98fe8

我们将FJFPerson类地址与ISA_MASK掩码进行位数的比对:

0x0000000  1  0  2  f  1   4    f   e    8
0x0000000  f  f  f  f  f   f    f   f    8

我们会发现ISA_MASK掩码为1的位数是大于类地址的位数,而后面的8即二进制的1000是因为字节对齐而补上的000,同时我们将其他几个类地址打印输出看一下:

其他类地址.png

这里我们可以推导出,类地址实例对象的isa指针ISA_MASK掩码相与后得到的地址,所以当类地址再次和ISA_MASK掩码相与之后得到的肯定是类地址本身

因此,这道题目之所以能够正常的调用,就在于类地址的取值的设计,而上面说的如果某个地址指向类对象的地址,我们可以把这个地址当做对象去用的说法,是有点牵强的,至少在arm64架构上是这样。

测试demo

三.补充

有简友说这道题目的打印是个巧合,并建议我看一下release的相关汇编的堆栈。
这里我分别打印了 真机 iOS 13、iOS 12.2 、iOS9 release版本的汇编,模拟器 iPhone8 iOS13 release版本的汇编模拟器 iPhone8 iOS11.2版本的汇编.

分析如下:

1. 真机 iOS 13 、 iOS 13、iOS 12.2 、iOS9 release版本的汇编(正常输出)

汇编代码:

FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
    0x102b165e0 <+0>:   sub    sp, sp, #0x30             ; =0x30           将sp-0x30,开辟栈空间
    0x102b165e4 <+4>:   stp    x29, x30, [sp, #0x20]                       将x29,x30放在(x29栈帧地址、x30程序链接寄存器)(sp+0x20)的地址处
    0x102b165e8 <+8>:   add    x29, sp, #0x20            ; =0x20           将sp+0x20栈地址赋值给x29
->  0x102b165ec <+12>:  nop                                                空指令
    0x102b165f0 <+16>:  ldr    x8, #0x28e0               ; (void *)0x0000000102b18ee0: ViewController 将0x28e0的地址给x8
    0x102b165f4 <+20>:  stp    x0, x8, [sp, #0x10]        将x0, x8放入(sp+0x10)的地址处,x0是self(当前viewController地址),x8是字符串(“ ViewController")
    0x102b165f8 <+24>:  nop                                               空指令
    0x102b165fc <+28>:  ldr    x1, #0x2824               ; "viewDidLoad”  将0x2824的值赋值给x1
    0x102b16600 <+32>:  add    x0, sp, #0x10             ; =0x10 将sp+0x10的值赋值给x0
    0x102b16604 <+36>:  bl     0x102b16990              ; symbol stub for: objc_msgSendSuper2 调用objc_msgSendSuper2
    0x102b16608 <+40>:  nop                                              空指令
    0x102b1660c <+44>:  ldr    x0, #0x288c               ; (void *)0x0000000102b18fa8: FJFPerson将0x288c的值赋值给ldr
    0x102b16610 <+48>:  nop                                              空指令
    0x102b16614 <+52>:  ldr    x1, #0x2814               ; "class”       将0x2814的值赋值给x1
    0x102b16618 <+56>:  bl     0x102b16984              ; symbol stub for: objc_msgSend 调用objc_msgSend
    0x102b1661c <+60>:  mov    x29, x29                                  将x29的值赋值给x29,恢复栈
    0x102b16620 <+64>:  bl     0x102b169a8              ; symbol stub for: objc_retainAutoreleasedReturnValue 调用autorelase
    0x102b16624 <+68>:  str    x0, [sp, #0x8]                            将x0的值存储到sp+0x8的位置  (void *)0x0000000102e80f80: FJFPerson即为cls
    0x102b16628 <+72>:  nop                                              空指令
    0x102b1662c <+76>:  ldr    x1, #0x2804               ; "print”       调用print
    0x102b16630 <+80>:  add    x0, sp, #0x8              ; =0x8 
    0x102b16634 <+84>:  bl     0x102b16984              ; symbol stub for: objc_msgSend
    0x102b16638 <+88>:  ldr    x0, [sp, #0x8]
    0x102b1663c <+92>:  bl     0x102b1699c              ; symbol stub for: objc_release
    0x102b16640 <+96>:  ldp    x29, x30, [sp, #0x20]
    0x102b16644 <+100>: add    sp, sp, #0x30             ; =0x30 
    0x102b16648 <+104>: ret   

堆栈信息:

iPhone真机堆栈

寄存器信息:

寄存器信息.png

2. 模拟器 iPhone8 iOS13 release版本的汇编(正常输出)

FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
    0x10dfde4cc <+0>:   pushq  %rbp                      rdp入栈
    0x10dfde4cd <+1>:   movq   %rsp, %rbp               将rsp的值赋值给rdp
    0x10dfde4d0 <+4>:   pushq  %r14                       r14入栈 
    0x10dfde4d2 <+6>:   pushq  %rbx                       rbx入栈 rbx为self的地址值
    0x10dfde4d3 <+7>:   subq   $0x20, %rsp                rsp - 0x20的值给rsp
    0x10dfde4d7 <+11>:  leaq   -0x28(%rbp), %rax          将rdp-0x28的地址给rax
    0x10dfde4db <+15>:  movq   %rdi, (%rax)                将rdi的值赋值给rax所在地址空间 rdi是self的地址值
    0x10dfde4de <+18>:  movq   0x29fb(%rip), %rcx        ; (void *)0x000000010dfe0ef8: ViewController 将字符串(viewController)赋值给rcx
    0x10dfde4e5 <+25>:  movq   %rcx, 0x8(%rax)            将rcx的值赋值给(rax+0x8),也就是字符串”viewController”赋值给rax+0x8所在地址
    0x10dfde4e9 <+29>:  movq   0x2940(%rip), %rsi        ; “viewDidLoad”,将字符串”viewDidLoad”的值赋值给rsi
    0x10dfde4f0 <+36>:  movq   %rax, %rdi                将rax的值赋值给rdi, rdi的值为rdp-0x28
    0x10dfde4f3 <+39>:  callq  0x10dfde85e              ; symbol stub for: objc_msgSendSuper2 调用objc_msgSendSuper2
->  0x10dfde4f8 <+44>:  movq   0x29a9(%rip), %rdi        ; (void *)0x000000010dfe0fc0: FJFPerson
    0x10dfde4ff <+51>:  movq   0x2932(%rip), %rsi        ; "class"
    0x10dfde506 <+58>:  movq   0x1afb(%rip), %r14        ; (void *)0x00007fff503b1780: objc_msgSend
    0x10dfde50d <+65>:  callq  *%r14                       调用objc_msgSend
    0x10dfde510 <+68>:  movq   %rax, %rdi                将rax的值赋值给rdi,rax即为cls
    0x10dfde513 <+71>:  callq  0x10dfde86a              ; symbol stub for: objc_retainAutoreleasedReturnValue 调用autorelease
    0x10dfde518 <+76>:  leaq   -0x18(%rbp), %rbx        将(rbp-0x18)的地址值给rbx
    0x10dfde51c <+80>:  movq   %rax, (%rbx)              将rax的值放入rbx所在的地址值,cls放在(rbp-0x18)
    0x10dfde51f <+83>:  movq   0x291a(%rip), %rsi        ; "print" 将”print“地址给rsi
    0x10dfde526 <+90>:  movq   %rbx, %rdi                将rbx的地址给rdi,即rdi的地址为(rbp-0x18)
    0x10dfde529 <+93>:  callq  *%r14
    0x10dfde52c <+96>:  movq   (%rbx), %rdi
    0x10dfde52f <+99>:  callq  *0x1adb(%rip)             ; (void *)0x00007fff503cb040: objc_release
    0x10dfde535 <+105>: addq   $0x20, %rsp
    0x10dfde539 <+109>: popq   %rbx
    0x10dfde53a <+110>: popq   %r14
    0x10dfde53c <+112>: popq   %rbp
    0x10dfde53d <+113>: retq  

堆栈信息:

堆栈信息.png

寄存器信息:

寄存器信息.png

3. 模拟器 iPhone8 iOS11.2版本的汇编 (崩溃)

汇编代码

FJFRuntimeInterviewQuestionDemo`-[ViewController viewDidLoad]:
    0x104ce74cc <+0>:   pushq  %rbp                        rdp入栈
    0x104ce74cd <+1>:   movq   %rsp, %rbp                  将rsp的值赋值给rdp
    0x104ce74d0 <+4>:   pushq  %r14                        r14入栈  _UIApplicationLinkedOnVersion
    0x104ce74d2 <+6>:   pushq  %rbx                        rbx入栈     "count" 是char类型的字符串
    0x104ce74d3 <+7>:   subq   $0x20, %rsp                 rsp - 0x20的值给rsp
    0x104ce74d7 <+11>:  leaq   -0x28(%rbp), %rax           将rdp-0x28的地址给rax
    0x104ce74db <+15>:  movq   %rdi, (%rax)                将rdi的值赋值给rax所在地址空间
    0x104ce74de <+18>:  movq   0x29fb(%rip), %rcx        ; (void *)0x0000000104ce9ef8: ViewController 将字符串(viewController)赋值给rcx
    0x104ce74e5 <+25>:  movq   %rcx, 0x8(%rax)             将rcx的值赋值给rax+0x8地址
    0x104ce74e9 <+29>:  movq   0x2940(%rip), %rsi        ; “viewDidLoad" 将字符串”viewDidLoad”的值赋值给rsi
    0x104ce74f0 <+36>:  movq   %rax, %rdi                   将rax的值赋值给rdi
    0x104ce74f3 <+39>:  callq  0x104ce785e              ; symbol stub for: objc_msgSendSuper2 调用objc_msgSendSuper2
->  0x104ce74f8 <+44>:  movq   0x29a9(%rip), %rdi        ; (void *)0x0000000104ce9fc0: FJFPerson 将FJFPerson地址给rdi
    0x104ce74ff <+51>:  movq   0x2932(%rip), %rsi        ; “class" 将字符串”class“的地址值给rsi
    0x104ce7506 <+58>:  movq   0x1afb(%rip), %r14        ; (void *)0x000000010560b940: objc_msgSend 将objc_msgSend给r14
    0x104ce750d <+65>:  callq  *%r14                      调用objc_msgSend
    0x104ce7510 <+68>:  movq   %rax, %rdi                将rax给rdi,rax为cls
    0x104ce7513 <+71>:  callq  0x104ce786a              ; symbol stub for: objc_retainAutoreleasedReturnValue 调用objc_retainAutoreleasedReturnValue
    0x104ce7518 <+76>:  leaq   -0x18(%rbp), %rbx        将(rbp-0x18)的值给rbx,
    0x104ce751c <+80>:  movq   %rax, (%rbx)             将rax的值放入rbx指向的地址空间
    0x104ce751f <+83>:  movq   0x291a(%rip), %rsi        ; “print” 将”print“地址给rsi
    0x104ce7526 <+90>:  movq   %rbx, %rdi                将rbx的值给rdi
    0x104ce7529 <+93>:  callq  *%r14                     调用objc_msgSend
    0x104ce752c <+96>:  movq   (%rbx), %rdi              
    0x104ce752f <+99>:  callq  *0x1adb(%rip)             ; (void *)0x0000000105608cc0: objc_release
    0x104ce7535 <+105>: addq   $0x20, %rsp
    0x104ce7539 <+109>: popq   %rbx
    0x104ce753a <+110>: popq   %r14
    0x104ce753c <+112>: popq   %rbp
    0x104ce753d <+113>: retq 

堆栈日志:

堆栈日志.png

寄存器信息:

寄存器信息.png

从以上测试结果,我们可以看出,是否崩溃取决于在cls之前入栈的值是否是NSObject类型,能否打印出my name is <ViewController: 0x7ff0aac05df0>,根本在于cls之前入栈的是否是self的地址。

因此之前的打印结果是ViewController对象的原因:
因为cls在栈上的数据结构符合它作为真实类的数据结构,self.name的地址正好是self对象的地址,这个说法并没有错误。

最后

如果大家有什么疑问或者意见向左的地方,欢迎大家留言讨论。

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

推荐阅读更多精彩内容