今天在群里看到大佬们在讨论一个面试题,问如下代码在 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 为什么可以解决循环引用

block 的内存结构衍生出来的面试题的更多相关文章

  1. 从汇编代码理解 Block 的内存结构

    ❓ 在断点调试 iOS 程序碰到 block 作为函数的形参时,如果想知道该 block 本身的函数签名信息和函数体地址时,有哪些办法?

  2. block本质探寻一之内存结构

    一.代码——命令行模式 //main.m #import <Foundation/Foundation.h> struct __block_impl { void *isa; int Fl ...

  3. Oracle之内存结构(SGA、PGA)

    一.内存结构 SGA(System Global Area):由所有服务进程和后台进程共享: PGA(Program Global Area):由每个服务进程.后台进程专有:每个进程都有一个PGA. ...

  4. [转]oracle学习入门系列之五内存结构、数据库结构、进程

    原文地址:http://www.2cto.com/database/201505/399285.html 1 Oracle数据库结构 关于这个话题,网上一搜绝对一大把,更别提书籍上出现的了,还有很多大 ...

  5. Java内存结构、类的初始化、及对象构造过程

    概述 网上关于该题目的文章已经很多,我觉得把它们几个关联起来讲可能更好理解一下.与其它语言一样,它在执行我们写的程序前要先分配内存空间,以便于存放代码.数据:程序的执行过程其实依然是代码的执行及数据的 ...

  6. [转载] Oracle之内存结构(SGA、PGA)

    2011-05-10 14:57:53 分类: Linux 一.内存结构 SGA(System Global Area):由所有服务进程和后台进程共享: PGA(Program Global Area ...

  7. JVM内存结构之堆、栈、方法区以及直接内存、堆和栈区别

    JVM内存结构之堆.栈.方法区以及直接内存.堆和栈区别 一.  理解JVM中堆与栈以及方法区 堆(heap):FIFO(队列优先,先进先出):二级缓存:*JVM中只有一个堆区被所有线程所共享:对象和数 ...

  8. oracle内存结构

    一.内存结构 SGA(System Global Area):由所有服务进程和后台进程共享: PGA(Program Global Area):由每个服务进程.后台进程专有:每个进程都有一个PGA. ...

  9. JAVA 对象内存结构

    JAVA对象内存结构 HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header).实例数据(Instance Data)和对齐填充(Padding). 对象头 markWo ...

随机推荐

  1. 一次生产环境搭建11g RAC的记录

    一.使用惠普3par工具配置共享存储 该部分可由惠普工作人员协助配置,只需将需求告知即可.如果想自己配置,惠普厂商会发送相关的软件工具以及操作手册给用户. 用putty登陆共享存储,使用showpd ...

  2. CSS 权重图

    关系图 图片出处我找不到了. 结论 权重从高到低排序 1. !important 2. style 3. #id 4. .class .child-class 5. .class1.class2 6. ...

  3. Spring注解 - 组件的注册

    Spring Boot的出现极大的简化了我们的开发,让我们无需再写繁杂的配置文件,其正是利用了注解的便捷性,而Spring Boot又依赖于Spring,因此深入学习Spring的注解是十分必要的. ...

  4. 工具之scroolToIndex

    需求定位:导航中实现子元素滚动到父元素的最左侧 解决方案:查找该子元素的offsetLeft值,然后让父元素滚动offsetLeft,parenDom.scrollLeft = childDom.of ...

  5. js 小练习题

    <script> /*1.结论,IIFE中运行顺序3,1,执行test(4),会传递参数*/ /*var a=5; var test = (function(a){ console.log ...

  6. 从火箭发场景来学习Java多线程并发闭锁对象

    从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size—这种方式来实现.但是在多线程并发的情 ...

  7. vue项目 github 上传项目并链接地址

    git init git init: 通过命令git init把这个文件夹变成Git可管理的仓库git status git status:查看当前仓库状态 git add . 这里提示你虽然把项目粘 ...

  8. tf.slice()函数详解(极详细)

    目录 1.官方注释 2.参数解释 3.例子 参考 @(tf.slice()函数详解 ) tf.slice()是TensorFlow库中分割张量的一个函数,其定义为def slice(input_, b ...

  9. Kafka的面试题

    1.Kafka的设计是什么样的呢?Kafka将消息以topic为单位进行归纳将向Kafka topic发布消息的程序成为producers.将预订topics并消费消息的程序成为consumer.Ka ...

  10. JS事件冒泡及阻止

    事件冒泡及阻止 当一个元素接收到事件的时候,会把他接收到的事件传给自己的父级,一直到window,当然其传播的是事件,绑定的执行函数并不会传播,如果父级没有绑定事件函数,就算传递了事件,也不会有什么表 ...