源代码:
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语言规范。
}