为什么nil / NULL块在运行时会导致总线错误?

我开始使用块,很快就注意到,无块会导致总线错误:

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

这似乎违背了Objective-C通常的行为,忽略消息到零对象:

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

因此,在使用块之前,我必须使用通常的无效检查:

 if (aBlock != nil) aBlock(); 

或者使用虚拟块:

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

还有其他的select吗? 是否有一个为什么零块不能简单地是一个nop?

我想解释一下这个更完整的答案。 首先让我们考虑这个代码:

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

如果你运行这个,那么你会看到类似这样的block()行的崩溃(当在32位架构上运行时 – 这很重要):

EXC_BAD_ACCESS(code = 2,address = 0xc)

那么,为什么呢? 那么, 0xc是最重要的一点。 崩溃意味着处理器试图读取内存地址0xc处的信息。 这绝对是一个完全不正确的事情。 这不太可能。 但是为什么它试图读取这个内存位置呢? 那么,这是由于实际上在一个引擎盖下build造一个块的方式。

当定义一个块时,编译器实际上在这个表单上创build一个堆栈结构:

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

该块是一个指向这个结构的指针。 第四个成员invoke这个结构是有趣的。 它是一个函数指针,指向块的实现所在的代码。 所以当一个块被调用时,处理器会尝试跳转到那个代码。 请注意,如果您计算invoke成员之前的结构中的字节数,则会发现十进制数为12,或hex数为C.

所以当一个块被调用时,处理器将获得该块的地址,并加上12,并尝试加载该存储器地址处的值。 然后它试图跳到那个地址。 但是如果块是零,那么它会尝试读取地址0xc 。 这是一个duff地址,很清楚,所以我们得到了分段错误。

现在,它必须像这样崩溃的原因,而不是像Objective-C消息调用那样静静地失败,确实是一个deviseselect。 由于编译器正在做决定如何调用块的工作,因此在调用块的任何地方都必须注入无检查代码。 这会增加代码的大小,导致性能下降。 另一种select是使用蹦床来做零检查。 但是这也会导致性能损失。 Objective-C消息已经通过了一个蹦床,因为它们需要查找实际被调用的方法。 运行时允许懒惰地注入方法和改变方法实现,所以它已经通过一个蹦床了。 在这种情况下,进行零检查的额外处罚并不重要。

我希望能够帮助我们解释一下原理。

有关更多信息,请参阅我的博客 文章 。

马特·加洛韦的答案是完美的! 太棒了!

我只想补充一点,有一些方法可以让生活更轻松。 你可以像这样定义一个macros:

 #define BLOCK_SAFE_RUN(block, ...) block ? block(__VA_ARGS__) : nil 

它可能需要0 – n个参数。 使用示例

 typedef void (^SimpleBlock)(void); SimpleBlock simpleNilBlock = nil; SimpleBlock simpleLogBlock = ^{ NSLog(@"working"); }; BLOCK_SAFE_RUN(simpleNilBlock); BLOCK_SAFE_RUN(simpleLogBlock); typedef void (^BlockWithArguments)(BOOL arg1, NSString *arg2); BlockWithArguments argumentsNilBlock = nil; BlockWithArguments argumentsLogBlock = ^(BOOL arg1, NSString *arg2) { NSLog(@"%@", arg2); }; BLOCK_SAFE_RUN(argumentsNilBlock, YES, @"ok"); BLOCK_SAFE_RUN(argumentsLogBlock, YES, @"ok"); 

如果你想获得返回值,并且你不确定块是否存在,那么你可能最好input:

 block ? block() : nil; 

这样您可以轻松定义回退值。 在我的例子'零'。

警告:我不是Blocks的专家。

objective-c对象,但调用块不是一个消息 ,尽pipe你仍然可以尝试[block retain]一个nil块或其他消息。

希望,(和链接)帮助。

这是我最简单的最好的解决scheme…也许有可能用这些c var-args写一个通用运行函数,但我不知道如何写这个。

 void run(void (^block)()) { if (block)block(); } void runWith(void (^block)(id), id value) { if (block)block(value); }