block系列之block的实现原理

#include <stdio.h>
int main()
{
  void (^blk)(void) = ^{
    printf("Hello, World!\n");
  };
  return 0;
}

为了研究编译器是如何实现 block 的,我们需要使用 clangclang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的,借此可以研究 block 具体的源码实现方式。该命令是:

clang -rewrite-objc block.c

转化之后,生成block.app:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

blockclang 编译器编译之后,生成了一个 __block_impl 结构体, isa 指针表明了 block 可以是一个对象,而 FuncPtr 指针显然是 block 对应的函数指针。

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;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  printf("Hello, World!\n");
}

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 (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

下面我们就具体看一下是如何实现的, __main_block_impl_0 就是该 block 的实现,从中我们可以看出:

  • __main_block_impl_0 中包含了两个成员变量和一个构造函数,成员变量分别是 __block_impl 结构体和描述信息 __main_block_desc_0 ,之后在构造函数中初始化 block 的类型信息和函数指针等信息。
  • __main_block_func_0 函数,即 block 对应的函数体。该函数接受一个 __cself 参数,即对应的 block 自身。
  • __main_block_desc_0 结构体,其中 Block_size 存储 block 大小。
    从上面代码,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体。

例子2:

block如何捕获变量

int main()
{
  int i = 1024;
  void (^blk)(void) = ^{
    printf("%d",i);
  };
  blk();
  return 0;
}

通过clang编译之后:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy
  printf("%d",i);
}

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()
{
    int i = 1024;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

从中可以看出这次的 block 结构体 __main_block_impl_0 多了个成员变量 i ,用来存储使用到的局部变量 i 。当在 block 中引用的变量 i 的时候,实际是在声明 block 时,被 copy__main_block_impl_0 结构体中的那个成员变量 i ,并且把 __cself 指针指向成员变量 i

如果尝试修改局部变量,编译会报错:

错误原因告诉我们变量不可赋值,也提醒我们要使用__block类型标识符。

因为 main 函数中的局部变量 i 和函数 __main_block_func_0 不在同一个作用域中,调用过程中只是进行了值传递。当然,在上面代码中,我们可以通过指针来实现局部变量的修改。不过这是由于在调用 __main_block_func_0 时, main 函数栈还没展开完成,变量 i 还在栈中。但是在很多情况下, block 是作为参数传递以供后续回调执行的。通常在这些情况下, block 被执行时,定义时所在的函数栈已经被展开,局部变量已经不在栈中了已经被销毁了,再用指针访问就会报常见的 坏内存访问

例子3:

__block 类型变量是如何支持修改。

int类型变量加上 __block 指示符,使得变量i可以在block函数体中被修改:

int main()
{
    __block int i = 1024;
    void (^blk)(void) = ^{
        i = 1023;
        printf("%d",i);
    };
    blk();
    return 0;
}

通过clang编译之后:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
  (i->__forwarding->i) = 1023;
  printf("%d",(i->__forwarding->i));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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()
{
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

从中可以看出,多出了一个 __Block_byref_i_0 的结构体:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
  • __isa 指针也可以知道 __Block_byref_i_0 也可以是对象。
  • __forwarding 指针指向 __Block_byref_i_0
  • 成员变量 i ,用来存储使用到的局部变量 i

__main_block_impl_0 对应的结构体:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
  (i->__forwarding->i) = 1023;
  printf("%d",(i->__forwarding->i));
}
  • __main_block_impl_0 的成员变量 i 变成了 __Block_byref_i_0* 指针类型。
  • __Block_byref_i_0 指针类型变量 i ,通过其成员变量 __forwarding 指针来操作另一个成员变量。

注意:

__Block_byref_i_0 类型变量i仍然处于栈上,当block被回调执行时,变量i所在的栈已经被展开。

查看 __main_block_desc_0 结构体,发现:

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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};

此时, __main_block_desc_0 多了两个成员函数: copydispose ,分别指向 __main_block_copy_0__main_block_dispose_0

block 从栈上被 copy 到堆上时,会调用 __main_block_copy_0__block 类型的成员变量i从栈上复制到堆上;而当 block 被释放时,相应地会调用 __main_block_dispose_0 来释放 __block 类型的成员变量i。如果栈上和堆上同时对该变量进行操作, __forwarding 的作用就体现出来了,当一个 __block 变量从栈上被复制到堆上时,栈上的那个 __Block_byref_i_0 结构体中的 __forwarding 指针也会指向堆上的结构。

我来评几句
登录后评论

已发表评论数()