为什么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); }