block底层实现

最近读书,关于block的底层实现,有以下思考和总结

  • c++部分的相关步骤分析写在代码注释
一、block是什么

1.首先写一个简单的block

#import <stdio.h>

int main(void) {
    
    void (^block)(void) = ^{
        printf("hello world!");
    };
    
    block();
    
    return 0;
}

2.将main.m 编译后 clang -rewite-objc main.m 生成 .cpp 文件

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
/**
3.参数fp即传入的参数__main_block_func_0函数的地址,赋值给结构体实例 impl 的属性 FuncPtr
4.参数desc即传入的 &__main_block_desc_0_DATA结构体取地址赋值给了结构体指针 Desc
5.结构体实例 impl 的 isa 指针存放了 _NSConcreteStackBlock类的地址
6._NSConcreteStackBlock是在将block作为OC对象处理时,该类的信息放置于_NSConcreteStackBlock 中,
由此可见,block的实质是OC对象
*/
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


        printf("hello world!");
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(void) {

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/**
对应代码
void (^block)(void) = ^{
    printf("hello world!");
};
1.等号左边 void (*block)(void)是一个无参无返的函数指针(是一个指针,指向函数)
2.等号右边 __main_block_impl_0 首先,c++结构体包含自己的属性,构造方法,成员方法,
所以 &__main_block_impl_0()是对结构体构造函数取地址,
函数的参数是 (void *)__main_block_func_0 和 &__main_block_desc_0_DATA
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
/**
对应代码 block();
7.前半部分((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
访问结构体 __block_impl自己的成员变量FuncPtr,
FuncPtr在步骤3被赋值了 __main_block_func_0(block执行代码块)函数的地址
8.后半部分((__block_impl *)block),将block自身作为参数传递
*/

    return 0;
}

3.引入变量

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    block();
    
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
/**
2.在c++中 a(_a) 表示 _a 这个形参赋值给 a 这个实参
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
/**
3.定义了一个新的变量,接收结构体成员变量a的值,__cself->a 表示结构体访问自己的属性。
*/
        printf("%d\n",a);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(void) {

    int a = 10;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
/**
1.将变量a的值传入,并赋值给了结构体成员变量
*/

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

4.__block变量

int main(void) {
    
    __block int a = 10;
    void (^block)(void) = ^{
        a += 10;
        printf("%d\n",a);
    };
    
    block();
    
    return 0;
}
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;  //由步骤2得出指向的结构体本身
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
/**
4.将传入的结构体指针的成员变量__forwarding的值(地址),赋值给当前结构体成员变量结构体指针a,保存了__block变量地址
*/
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
/**
5.访问结构体成员变量a,赋值给新的结构体指针
*/
        (a->__forwarding->a) += 10;
/**
6.a->__forwarding首先找到结构体指针的指向,a->__forwarding->a获取到有效成员变量a的值并进行修改
*/
        printf("%d\n",(a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
7.栈copy到堆时调用,因为结构体成员变量包含所截获的变量或者__block变量结构体指针,所以copy的时候也一起copy了
*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
/**
8.堆上的block废弃时调用
*/
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {

    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
/**
1.变量a不再单纯的是一个int类型,变成了__Block_byref_a_0结构体类型,并分别传入的参数进行赋值
2.第二个参数(__Block_byref_a_0 *)&a,结构体指针,值是a的地址,a又是结构体__Block_byref_a_0,
所以__Block_byref_a_0中的结构体指针__forwarding指向自身
3.C++中,__attribute__ 表示可以设置函数属性。
理解一下字面意思大概知道内部发生了什么
byref 是把内存地址告诉程序,所以改变的直接就是内存中的数值。按地址传递参数使过程用变量的内存地址去访问实际变量的内容。结果,将变量传递给过程时,通过过程可永远改变变量值。
byval 是把内存数值的拷贝给程序,所以改变的只是拷贝,内存原来的值是不会改变的。按值传递参数时,传递的只是变量的副本。如果过程改变了这个值,则所作变动只影响副本而不会影响变量本身。
*/

    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

📍结论:
1.block是函数指针,指向的是block代码块编译时生成的函数地址。执行block相当于找到指向block的指针。
2.block对象是结构体
(1)有isa指针(impl->isa)指向自己的类(_NSConcreteStackBlock)
(2)有desc结构体描述block的信息
(3)__forwarding((__Block_byref_a_0 *)a -> __forwarding)指向自己或堆上自己的地址
(4)当block对象截获变量,变量也会出现在block结构体中(int a)
(5)指向block代码块的函数指针(impl.FuncPtr = fp)。
(6)block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的__block变量。(__block对象也会转变成结构体)
3.block代码块在编译的时候会生成一个函数,函数的参数是block对象结构体指针。执行block,相当于通过block的地址找到__block变量结构体指针,再找到变量值,进行操作。

二、block的类

1.block在编译中,会被当成结构体处理,block实际结构

structBlock_literal_1{
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    intflags;
    intreserved;
    void(*invoke)(void *,...);    //block执行时调用的函数指针,block定义时内部的执行代码
    structBlock_descriptor_1{
        unsigned long intreserved;         // NULL
        unsigned long intsize;         // sizeof(struct Block_literal_1)
        // optional helper functions
         void(*copy_helper)(void *dst, void *src);     // IFF (1<<25)
         void(*dispose_helper)(void *src);             // IFF (1<<25)
         // required ABI.2010.3.16
         const char *signature;                         // IFF (1<<30)
     } *descriptor;
     // imported variables
};

2.block的类有三种

block的类 设置对象的存储域
_NSConcreteGlobalBlock 程序的数据区域(.data区)
_NSConcreteStackBlock
_NSConcreteMallocBlock
应用程序的内存分配

(1)_NSConcreteGlobalBlock

#import <Foundation/Foundation.h>

void (^block)(void) = ^{
    
};

int main(void) {
    
    void (^block1)(void) = ^{
        
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%s %p",object_getClassName(block1),block1);
    
    return 0;
}

//输出
//__NSGlobalBlock__ 0x100001058
//__NSGlobalBlock__ 0x100001098

(2)_NSConcreteStackBlock
当block引入变量,变量内存地址会发生如下变化

#import <Foundation/Foundation.h>

int main(void) {
    
    int a = 10;
    void (^block)(void) = ^{
        printf("%d\n",a);
    };
    
    NSLog(@"%s %p",object_getClassName(block),block);
    NSLog(@"%@",^{
        printf("%d\n",a);
    });
    
    return 0;
}
//输出
//__NSMallocBlock__ 0x100443c80
//<__NSStackBlock__: 0x7fff5fbff6b0>

(3)_NSConcreteMallocBlock
从上一步可以看出,block在被赋值后,从栈来到了堆,这段代码是从栈copy到堆的过程

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申请block的堆内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷贝栈中block到刚申请的堆内存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改变isa指向_NSConcreteMallocBlock,即堆block类型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

block是默认分配在栈上的唯一OC对象,因为编译器更倾向于在栈上分配空间,因为执行效率较高,但是只能是在已知实际大小的情况下去分配,只有简单的值(比如指针)可以被分配在栈上,block的大小是确定的,你创建了一个给定的block就不能修改, 在整个执行过程中block不变, 它是需要快速执行的代码段,因此它是栈的最佳候选。

📍结论:
1.定义在函数外的block和定义在函数内部且没有捕获自动变量的block是全局block。
2.ARC下并且有变量捕获的情况下,对block自动执行了copy,将block由栈---->copy---->堆
3.copy的过程中,主要实现,通过memmove函数将block内容进行copy,并且将 isa 指针指向了_NSConcreteMallocBlock

三、block的copy

1.自身的copy
(1)栈Block
栈block拷贝复制了内容,重置了isa指针指向,重置了flags参数

struct Block_layout *result = malloc(aBlock->descriptor->size);
      if (!result) return (void *)0;
      memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
      // reset refcount
      result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
      result->flags |= BLOCK_NEEDS_FREE | 1;
      result->isa = _NSConcreteMallocBlock;
      if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
          //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
          (*aBlock->descriptor->copy)(result, aBlock); // do fixup
      }
      return result;

(2)堆Block
改变引用计数

if (aBlock->flags & BLOCK_NEEDS_FREE) {
      // latches on high
      latching_incr_int(&aBlock->flags);
      return aBlock;
  }

(3)全局Block
直接返回了传入的block

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
      return aBlock;
  }

2.__block变量的copy
(1)__block修饰的基本数据类型变量
会生成__Block_byref_a_0结构体,__block将基本数据类型包装成对象,并且只在最初block拷贝时复制一次,后面的拷贝只会增加这个捕获变量的引用计数。
(2)没有用__block修饰的对象变量

    NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_8ef076_mi_0,obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

并没有生成__Block_byref_a_0结构体,在_Block_object_assign中对应的判断代码:

else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
    _Block_retain_object(object);
    _Block_assign((void *)object, destAddr);
}

通过以上代码可以看出,对对象进行了retain,操作了对象的引用计数
(3)用__block修饰的对象变量

    __block NSObject *obj = [[NSObject alloc] init];
    void(^block)(void) = ^{
        NSLog(@"%@",obj);
    };
    block();
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_obj_0 *obj = __cself->obj; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_f7_jctrsbb11gb_vzv3gzmszy1h0000gn_T_main_ae30ca_mi_0,(obj->__forwarding->obj));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(void) {
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}

生成了__Block_byref_obj_0结构体,并且结构体中多了两个成员函数,void (*__Block_byref_id_object_copy)(void*, void*);void (*__Block_byref_id_object_dispose)(void*); 用来实现对对象的内存管理。copy/dispose可以理解为retain(copy)/release

四、block的循环引用
#import "Person.h"

typedef void(^block)(void);

@implementation Person
{
    block _blk;
}

-(void)test
{
    _blk = ^{
        NSLog(@"%@",self);  //⚠️warning - retainCycle
    };
}

@end

这段代码会报warning⚠️,因为造成循环引用retainCycle


相互持有

解决
1.__weak一端变为弱引用(MRC下无效)

    __weak typeof(self)wself = self;
    _blk = ^{
        NSLog(@"%@",wself);
    };
__weak弱引用

2.引入__block变量(ARC下无效)
当block由栈copy到堆,若block使用的变量为附有__block说明符的id类型或对象类型的自动变量,不会被retain
这段代码在ARC下不会走dealloc方法

    __block id obj = self;
    _blk = ^{
        NSLog(@"%@",obj);
    };
引入__block变量

以上是我个人分析,有不合理的地方,欢迎指正

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