天天看点

Block 5:Block解析之截获局部变量值

    源代码:

int main(int argc, const char * argv[])
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (^blk)(void) = ^{printf(fmt, val);};

  val = 2;
  fmt = "These values were changed. val = %d\n";
  
  blk();
  
  return 0;
}
           

    将上面的源代码用命令:“clang -rewrite-objc main.c”转换为C++代码如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  printf(fmt, val);
}

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 argc, const char * argv[])
{
  int dmy = 256;
  int val = 10;
  const char *fmt = "val = %d\n";
  void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);

  val = 2;
  fmt = "These values were changed. val = %d\n";

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

  return 0;
}
           

    这与前面转换的源代码稍有差异。下面来看看其中的不同这处。首先我们注意到,Block语句中使用的局部变量被作为成员变量追加到了__main_block_impl_0结构体中。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
}
           

    __main_block_impl_0结构体内声明的成员变量类型与局部变量类型完全相同。请注意,Block语句中没有使用的局部变量不会被追加,如此源代码中的变量dmy。Blocks的局部变量截获只针对Block中使用的局部变量。下面来看看初始化该结构体实例的构造函数的差异。

void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
           

    使用执行Block语法时的局部变量fmt和val来初始化__main_block_impl_0结构体实例。

    即在该源代码中,__main_block_impl_0结构体实例的初始化如下:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = __main_block_desc_0_DATA;
fmt = "val = %d\n";
val = 10;
           

    由此可知,在__main_block_impl_0结构体实例(即Block)中,变量值被截获。

    下面再来看一下使用Block的匿名函数的实现。最被源代码的Block语法如下所示:

printf(fmt, val);
           

    该源代码可转换为以下函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  printf(fmt, val);
}
           

    在转换后的源代码中,截获到__main_block_impl_0结构体实例的成员变量上的局部变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动便可使用截获的局部变量值执行。

    总的来说,所谓“截获局部变量值”意味着在执行Block语法时,Block语法表达式所使用的局部变量值被保存到Block的结构体(即Block自身)中。

    然而,Block不能直接使用C语法数组类型的局部变量。如前所述,截获局部变量时,将值传递给结构体的构造函数进行保存。

    下面确认在Block中利用C语法数组类型的变量时有可能使用到的源代码。首先来看将数组传递给Block的结构体构造函数的情况。

void func(char a[10]) {
  char b[10] = a;
  printf("%d\n", b[0]);
}

int main() {
  char a[10] = {2};
  func(a);
}
           

    该源代码可以顺利编译,并正常执行。在之后的构造函数中,将参数赋给成员变量中,这样在变换了Block语法的函数内可由成员变量赋值给局部变量。源代码预测如下。

void func(char a[10]) {

  char b[10] = a;

  printf("%d\n", b[0]);

}

int main() {

  char a[10] = {2};

  func(a);

}

    该源代码将C语言数组类型变量赋值给C语言数组类型变量中,这是不能编译的。虽然变量的类型以及数组的大小都相同,但C语言规范不允许这种赋值。当然,有许多方法可以截获值,但Blocks似乎更遵循C语言规范。

}