为什么nil/NULL块在运行时会导致总线错误? [英] Why do nil / NULL blocks cause bus errors when run?

查看:99
本文介绍了为什么nil/NULL块在运行时会导致总线错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开始大量使用块,不久便注意到没有块会导致总线错误:

I started using blocks a lot and soon noticed that nil blocks cause bus errors:

typedef void (^SimpleBlock)(void);
SimpleBlock aBlock = nil;
aBlock(); // bus error

这似乎违背了Objective-C的通常行为,即忽略向零个对象发送的消息:

This seems to go against the usual behaviour of Objective-C that ignores messages to nil objects:

NSArray *foo = nil;
NSLog(@"%i", [foo count]); // runs fine

因此,在使用块之前,我必须求助于常规的nil检查:

Therefore I have to resort to the usual nil check before I use a block:

if (aBlock != nil)
    aBlock();

或使用虚拟块:

aBlock = ^{};
aBlock(); // runs fine

还有其他选择吗?为何没有零块不能仅仅是点子?

Is there another option? Is there a reason why nil blocks couldn’t be simply a nop?

推荐答案

我想进一步解释一下,并给出更完整的答案.首先让我们考虑以下代码:

I'd like to explain this a bit more, with a more complete answer. First let's consider this code:

#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {    
    void (^block)() = nil;
    block();
}

如果运行此命令,则会在block()行上看到类似以下的崩溃(在32位体系结构上运行时-这很重要):

If you run this then you'll see a crash on the block() line that looks something like this (when run on a 32-bit architecture - that's important):

EXC_BAD_ACCESS(代码= 2,地址= 0xc)

EXC_BAD_ACCESS (code=2, address=0xc)

那为什么呢?好吧,0xc是最重要的位.崩溃意味着处理器已尝试读取内存地址0xc处的信息.这几乎绝对是一件完全不正确的事情.那里什么都不太可能.但是,为什么它尝试读取此内存位置?嗯,这是由于实际上是在引擎盖下构造积木的方式造成的.

So, why is that? Well, the 0xc is the most important bit. The crash means that the processor has tried to read the information at memory address 0xc. This is almost definitely an entirely incorrect thing to do. It's unlikely there's anything there. But why did it try to read this memory location? Well, it's due to the way in which a block is actually constructed under the hood.

定义一个块后,编译器实际上会在堆栈上创建以下形式的结构:

When a block is defined, the compiler actually creates a structure on the stack, of this form:

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

然后,该块是指向此结构的指针.这个结构的第四个成员invoke是有趣的.它是一个函数指针,指向保存该块实现的代码.因此,在调用一个块时,处理器会尝试跳转到该代码.请注意,如果计算invoke成员之前的结构中的字节数,您会发现十进制为12,十六进制为C.

The block is then a pointer to this structure. The fourth member, invoke, of this structure is the interesting one. It is a function pointer, pointing to the code where the block's implementation is held. So the processor tries to jump to that code when a block is invoked. Notice that if you count the number of bytes in the structure before the invoke member, you'll find that there are 12 in decimal, or C in hexadecimal.

因此,当调用一个块时,处理器将获取该块的地址,将其加12,然后尝试加载该内存地址中保存的值.然后,它尝试跳转到该地址.但是,如果该块为nil,则它将尝试读取地址0xc.显然,这是一个达芙地址,所以我们遇到了分段错误.

So when a block is invoked, the processor takes the address of the block, adds 12 and tries to load the value held at that memory address. It then tries to jump to that address. But if the block is nil then it'll try to read the address 0xc. This is a duff address, clearly, and so we get the segmentation fault.

现在,它必须是这样的崩溃,而不是像Objective-C消息调用那样无声地失败的原因,实际上是一种设计选择.由于编译器正在做决定如何调用该块的工作,因此它必须在调用块的任何地方注入nil检查代码.这会增加代码大小并导致性能下降.另一种选择是使用蹦床进行零位检查.但是,这也会导致性能下降. Objective-C消息已经通过了蹦床,因为它们需要查找将实际调用的方法.运行时允许方法的惰性注入和方法实现的更改,因此无论如何它已经通过了蹦床.在这种情况下,执行nil检查的额外代价并不重要.

Now the reason it must be a crash like this rather than silently failing like an Objective-C message call does is really a design choice. Since the compiler is doing the work of deciding how to invoke the block, it would have to inject nil checking code everywhere a block is invoked. This would increase code size and lead to bad performance. Another option would be to use a trampoline which does the nil checking. However this would also incur performance penalty. Objective-C messages already go through a trampoline since they need to look up the method that will actually be invoked. The runtime allows for lazy injection of methods and changing of method implementations, so it's already going through a trampoline anyway. The extra penalty of doing the nil checking is not significant in this case.

我希望这能帮助您解释其基本原理.

I hope that helps a little bit to explain the rationale.

有关更多信息,请参见我的博客 帖子.

For more information, see my blog posts.

这篇关于为什么nil/NULL块在运行时会导致总线错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆