前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用Clang探究block捕获外部变量的本质(一)

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

作者头像
VV木公子
发布2019-11-12 19:37:13
7.8K0
发布2019-11-12 19:37:13
举报
文章被收录于专栏:TechBoxTechBox

前言

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

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

我们知道,Objective-C的block会捕获自动变量。在计算机编程领域,自动变量(Automatic Variable)指的是局部作用域变量,即局部变量。相对于全局变量。如下,在main函数中声明一个局部变量val = 1;block中打印val的值,然后在执行block前修改val = 2;, 但是block依旧输出1。这就是所谓的block会捕获自动变量。

本篇文章主要探究block捕获局部变量的底层原理。除去函数参数外,关于block和静态变量、全局变量、静态全局变量的关系将在后面的文章展开讨论。

Objective-C转C++

代码语言:javascript
复制
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++代码,如下:

代码语言:javascript
复制
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。

构造函数实现:

代码语言:javascript
复制
__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函数指针实现:

代码语言:javascript
复制
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对应的实现结构体中。

敬请期待~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Objective-C转C++
  • C++源码分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档