天天看點

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語言規範。

}