block 的内存结构衍生出来的面试题

今天在群里看到大佬们在讨论一个面试题,问如下代码在 32bit 和 64bit 系统上分别报什么错误:

#import <Foundation/Foundation.h>
int main()
{
    void (^block)(void) = nil;
    block();
    return 0;
}

虽然有大佬一下子说出了答案,但我仍然一脸懵逼,后来经人提醒,这个考察 block 在内存中的结构,于是赶紧做了如下实验终于弄懂了为什么。

  • 实验
    将以上代码保存为 block_test.m ,在命令行编译成 C++:clang -rewrite-objc -fobjc-arc block_test.m,打开同一目录下生成的block_test.cpp文件,截取如下关键代码:
// 注释①
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

int main()
{
    void (*block)(void) = __null;
    // 注释 ②
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
  • 解释:

    1. 注释①所在的结构体是 block 在内存中的数据结构。
    2. 注释②是一个函数调用语句,可以简化成:((__block_impl *)block)->FuncPtr(block),即调用 __block_impl 类型指针指向的结构体中 FuncPtr 指向的函数,由于该结构体指针指向的内容为 null,因此 FuncPtr指向的位置显然不是一个函数,所以该指针指向的位置会发生一个EXC_BAD_ACCESS 错误。
    3. FuncPtr 指向的位置是多少呢?根据上述__block_impl中各成员变量的排列可知 FuncPtr 的偏移是 sizeof(void *) + sizeof(int) + sizeof(int),另外还需要考虑结构体内存对齐,参考这个链接,所以在 32bit 系统中,偏移:4 + 4 + 4 = 0x0C;64bit 系统中,偏移:8 + 8 = 0x10
  • 结论:

    • 答案:在 32bit 系统中报错:EXC_BAD_ACCESS(address = 0x0C),在 64bit 系统中报错EXC_BAD_ACCESS(address = 0x10)
    • block 本质是一个结构体,在探究 block 相关的问题时,转换成 C++ 代码后往往就可以一目了然,比如weakSelf 为什么可以解决循环引用

猜你喜欢

转载自www.cnblogs.com/boch2436/p/12660809.html