美文网首页iOS 开发每天分享优质文章iOS学习笔记iOS开发记录
利用Clang探究block捕获外部变量的本质(一)

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

作者: VV木公子 | 来源:发表于2019-11-09 22:43 被阅读0次

前言

说到外部变量,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对应的实现结构体中。

敬请期待~

相关文章

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

    前言 说到外部变量,C语言中变量一般可以分为5种: 自动变量 函数参数 静态变量(指局部静态变量) 静态全局变量 ...

  • block底层原理探究(二):内存管理

    前篇block底层原理探究(一):捕获,我们探究了block捕获外部变量的原理;如果block捕获的是对象类型的a...

  • iOS相关知识(六)-- block

    1、block的本质 2、block的变量捕获(capture) 为了保证block内部能够正常访问外部的变量,b...

  • block中的捕获外部变量值的原理

    block中的捕获外部变量值的原理 为什么捕获的是变量的值 由block的本质可以知道,block转换之后是一个结...

  • Block

    Block Block 可以捕获外部变量 Block 可以捕获来自外部作用域的变量,这是Block一个很强大的特性...

  • Block 之 变量捕获

    为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,即捕获外部变量。 前言: 搞清成员变量、...

  • Block变量捕获详解(一)

    什么是Block变量捕获block变量捕获就是在block内部创建一个变量来存放外部变量什么是值捕获block将外...

  • OC基础-Block(2)

    OC基础-Block(2)Block的变量捕获为了保证block内部能够正常访问外部的变量,block有个变量捕获...

  • OC中的Block(二)

    block的变量捕获(capture) 为了保证block内部能够正常访问外部的变量,block有个变量捕获机制 ...

  • Objective - C block(二)block的类型及捕

    (一)block 捕获变量类型 为了保证block内部能够正确访问外部的变量,block有一个变量捕获机制 (1)...

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/jzcobctx.html