专栏首页TechBox利用Clang探究block捕获外部变量的本质(一)

利用Clang探究block捕获外部变量的本质(一)

前言

说到外部变量,C语言中变量一般可以分为5种:

  • 自动变量
  • 函数参数
  • 静态变量(指局部静态变量)
  • 静态全局变量
  • 全局变量

我们知道,Objective-C的block会捕获自动变量。在计算机编程领域,自动变量(Automatic Variable)指的是局部作用域变量,即局部变量。相对于全局变量。如下,在main函数中声明一个局部变量val = 1;block中打印val的值,然后在执行block前修改val = 2;, 但是block依旧输出1。这就是所谓的block会捕获自动变量。 本篇文章主要探究block捕获局部变量的底层原理。除去函数参数外,关于block和静态变量、全局变量、静态全局变量的关系将在后面的文章展开讨论。

Objective-C转C++

int main() {
    // block会捕获局部变量,下面执行执行block前后val分别输出2和1
    int val = 1;
    void (^block)(void) = ^{
        printf("执行block后val = %d, val地址 = %p\n", val, &val);
    };
    val = 2;
    printf("执行block前val = %d, val地址 = %p\n", val, &val);
    block();

    return 0;
    
<!--    控制台打印:-->
<!--    执行block前val = 2, val地址 = 0x7ffeefbff598-->
<!--    执行block后val = 1, val地址 = 0x100713770-->
<!--    Program ended with exit code: 0-->
}

这里有两个疑问:

  • block是如何实现捕获block外部局部变量的?
  • 为什么block执行前后val的内存地址不同?

以上源码经过Clang转为C++代码,如下:

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;
    int val;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : 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) {
    int val = __cself->val; // bound by copy
    printf("执行block后val = %d, val地址 = %p\n", val, &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 val = 1;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
    val = 2;
    printf("执行block前val = %d, val地址 = %p\n", val, &val);
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

C++源码分析

如果你读过笔者的利用Clang探究block的本质利用Clang探究__block的本质,那么本篇文章理解起来将会是切菜一样easy。甚至本篇文章变得有些多余。 以上代码看起来很熟悉,和利用Clang探究block的本质一样,依旧包括3个结构体和1个函数。唯一不同的是结构体__main_block_impl_0中多了一个成员变量int val;而函数__main_block_func_0中使用__cself->val又初始化了另一个局部变量val。 构造函数实现:

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

block函数指针实现:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int val = __cself->val; // bound by copy
    printf("执行block后val = %d, val地址 = %p\n", val, &val);
}

以上两段代码体现了block捕获局部变量的本质:编译器会根据上下文动态生成block实现的结构体__main_block_impl_0(之前文章有讲)。也会动态的在结构体中分配一个和局部变量同名同类型的变量val。在main函数中调用__main_block_impl_0构造函数时将局部变量val作为参数传递给__main_block_impl_0。__main_block_impl_0会把参数val保存在自己的成员变量val中。当执行block的函数实现__main_block_func_0时会把__main_block_impl_0实例作为入参传递给__main_block_func_0,__main_block_func_0将__main_block_impl_0的成员变量val保存的值取出后赋值给另一个临时变量val。这样就实现了block捕获外部变量。因为block在函数内部又创建了另一个临时变量val,这也验证了,为什么在block外和block内打印的val的内存地址不同。

也许你会疑问,为什么把val变量封装到了结构体__main_block_impl_0中,而不是结构体__block_impl中? 原因__block_impl这个结构体是“稳定的”、“不变的”。利用Clang探究block的本质 中说过,__block_impl这个结构体用来描述block的底层结构,包含4个成员变量。__block_impl是一个通用结构体,所谓通用是指其他block的底层结构依旧是__block_impl。如果一个Objective-C的文件中存在多个block,那么对应的C++文件依旧只存在一个__block_impl。而每个block的具体实现均对应一个类似于__main_block_impl_0的结构体,所以,把val封装到block对应的实现结构体中。

敬请期待~

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 利用Clang探究block的本质

    block作为Objective-C语言中的一种特殊的存在,已经为大家所熟知。在其他语言中,也有类似于block的实现,比如JavaScript和Swift中的...

    VV木公子
  • 【iOS】block小记

    VV木公子
  • 利用Clang探究__block的本质

    上一篇文章利用Clang探究block的本质得出的结论是block的本质是一个结构体对象,该对象包含调用block时要执行的函数指针。当我们在Objective...

    VV木公子
  • 《Objective-C-高级编程》干货三部曲(二):Blocks篇

    这一章讲解了Block相关的知识。因为作者将Objective-C的代码转成了C++的代码,所以第一次看的时候非常吃力,我自己也不记得看了多少遍了。

    用户1740424
  • 利用Clang探究block的本质

    block作为Objective-C语言中的一种特殊的存在,已经为大家所熟知。在其他语言中,也有类似于block的实现,比如JavaScript和Swift中的...

    VV木公子
  • block内部实现

    c. 我们先看一下__ main_block_impl_0结构体和 __ main_block_func_0 还有__main_block_desc_0_DAT...

    老沙
  • iOS block探究(一): 基础详解你要知道的block都在这里

    你要知道的block都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本文大纲 blo...

    WWWWDotPNG
  • 编码篇-Block里面的小天地

    Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性。 通常来说,block都是一些简短代码片段的封装,适...

    進无尽
  • Block 小结block  的 储存位置block  的循环引用

    陈雨尘
  • iOS block的一些理解和注意点

    大壮

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动