Block中为什么无法引用c语言数组

更新记录

时间 版本修改
2020年4月12日 初版

Block引用C语言数组报错

char text[] = "hello";
void (^blk)(void) = ^{
        printf("%c\n",text[2]);
};
  • 报编译错误:error:cannot refer to declaration with an array type inside block
  • 《Objective-C 高级编程 iOS与OS X多线程和内存管理》2.3.1 Block的实质中,提出解决方案,修改成如下代码:
char text[] = "hello";
int main(int argc, const char * argv[]) {
   void (^blk)(void) = ^{
        printf("%c\n",text[2]);
    };
    return 0;
}

报错的根本原因

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;              //block捕获的变量 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;
  }
};
  • 上述的block,捕获了一个const char*和一个int。所以构造函数中会多两个参数。
  • 上述就是目前编译器的实现,所以如果捕获一个C语言数组,会变成如下代码:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;              //block捕获的变量 val
  char szArray[10];
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, char _szArray[10], int flags=0) : fmt(_fmt), val(_val), szArray[10](_szArray) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 由于转换成了构造函数的参数列表的形式,实质上就是调用了szArray[10] = _szArray这样的语句。虽然变量的类型以及数组的大小都相同,但C语言规范不允许这种赋值。因此会报编译错误。

捕获指针替代捕获C语言数组

  • 上述可以正常编译的代码,实际对应的转换后的C/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;
    char *text;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_text, int flags=0) : text(_text) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    char *text = __cself->text; // bound by copy

    printf("%c\n",text[2]);
    
}

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[]) {
    char text[] = "hello";
    __main_block_impl_0 block_struct = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, text);
    void (*blk)() = (void (*)())&block_struct;
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
  • 因为对于指针的赋值text = _text是符合C语言语法规范的,因此这种用法是没有任何问题的。

阅读延申

char text[] = "hello";
int main(int argc, const char * argv[]) {
   void (^blk)(void) = ^{
        printf("%c\n",text[2]);
    };
    return 0;
}
  • 但是,它的原因是因为:这个Block对象里面使用的是全局变量,它是不需要存储到自己的Block结构体中,即它的结构体应该是如下结构:
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;

    //如果block截获了自动变量,会放置在这里。由于该block内引用的是全局变量,并不会在此加入字段进行初始化。

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_text, int flags=0) : text(_text) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 虽然博文的结果具有参考性,但是需要特别区分好这两种做法的底层特性。

参考资料

  • 《Objective-C 高级编程 iOS与OS X多线程和内存管理》2.3.1 Block的实质
  • 《Objective-C 高级编程 iOS与OS X多线程和内存管理》2.3.2 截获自动变量值
posted @ 2020-04-13 00:21  HelloWooo  阅读(723)  评论(0编辑  收藏  举报