最近比较忙,微信的暂时就没有更新了。好多人都在问我,block是怎么一回事情,我们怎么通过__block就可以修改外部变量的值呢?等等,好吧,那我就将我的研究成果简单的写一篇博客吧,同时感谢来自这里写链接内容的分享。
大家还记得无参block的申明和实现吧:
#include <stdio.h>
int main() {
void (^block)(void) = ^{ printf("asdn"); };
block();
return 0;
}
其实这已经是很上层的东西了,我们来看看他的中间代码。通过Clang可以查看他的中间代码:
这样后,我们会有一个test.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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("asdn"); }
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 (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
shit,一堆东西,看不懂对不对?
我们来简单的介绍一下这个东西,第一个结构体也就是该block的结构体。我们可以看到他有一个isa指针,它表明了这个block可以是任意数据类型的。然后注意到一个FuncPtr的函数指针,它指向了该block的函数实现地址。似乎明了了吧。然后我们来看看block是如何实现的。当我们申明了一个block:
void (^block)(void) = ^{ printf("asdn"); };
实际上在main函数中会转变成这样的代码:
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
what the fuck!又看不懂了。=号前面的我们不管,直接看
&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
这里实际上使用了
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;
}
};
结构体的一个构造函数。在这个结构体中还有两个成员变量,一个是block的结构体,另外一个是该结构体的描述信息。
而描述信息也是一个结构体,其中Block_size相对重要,它标志了__main_block_impl_0结构体的空间大小。
这时候我们注意到一个函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("asdn"); }
明白人一看就知道这个是该block的函数实现。注意一下这里有一个参数__cself,实际上就是block本身。
最后我们看block是如何调用的
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
可以看出,它就是调用了block结构体的FuncPtr函数,并将自己block作为参数传入进去。
我们将代码更改成:
#include <stdio.h>
int main() {
void (^block)(int) = ^(int i){ printf("%d",i); };
block(5);
return 0;
}
再获得中间代码后:
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int i) {
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() {
void (*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 5);
return 0;
}
注意到所有的地方都没有改变,只是在函数的实现中增加了一个参数。是不是言简意赅?
首先我们来看它是如何访问外部变量的:
#include <stdio.h>
int main() {
int i = 5;
void (^block)(void) = ^{ printf("%d",i); };
block();
return 0;
}
查看源码后会发现有这样的不同:
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 = 5;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
在__main_block_impl_0结构体中多出了一个int i;实际上他就是我们的外部变量的内容。在函数实现里面,我们会先将这个i也就是__cself->i赋给函数内的局部变量int i,然后打印i的值。
其次我们再来看看我们是如何改变外部变量的值,中所周知,我们会加上__block来修饰外部变量i
#include <stdio.h>
int main() {
__block int i = 5;
void (^block)(void) = ^{ i = 8;printf("%d",i); };
block();
return 0;
}
这时候我们会发现中间代码是这样的:
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) = 8;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), 5};
void (*block)(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 *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
这时候几乎是大变样了。但是我们只观察重点,我们会发现事实上__main_block_impl_0结构体重存了一个__Block_byref_i_0 *i;而__Block_byref_i_0结构体中有两个比较重要的内容,一个是int i,存放外部变量i的内容,而__forwarding结构体指针变量则是指向自己的。我们通过__forwarding访问i来改变i的值就可以实现改变了:
__Block_byref_i_0 *i = __cself->i; // bound by ref
(i->__forwarding->i) = 8;
#include <stdio.h>
int main() {
static int i = 5;
void (^block)(void) = ^{ i = 8;printf("%d",i); };
block();
return 0;
}
中间代码如下:
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
(*i) = 8;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() {
static int i = 5;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
相信不用我再多说什么了吧,你可以知道静态变量的话其实就是根据指针来修改内容了吧。
通过上面的演示,不知道各位对block是否有了一定的深入了解呢?很多时候,我们可以通过中间代码来探究原理性的知识。