为什么在if语句中设置Block变量会导致崩溃?

时间:2014-06-18 18:24:30

标签: objective-c crash runtime-error objective-c-blocks

我在“Effective Objective-C 2.0”一书中找到了一个块示例

void (^block)();
if (/* some condition */) {
    block = ^ {
        NSLog(@"Block A");
    };
} else {
    block = ^ {
        NSLog(@"Block B");
    };
}
block();

代码很危险,以下是书中的解释:

  

ifelse语句中定义的两个块在堆栈内存中分配。当它为每个块分配堆栈内存时,编译器可以在分配内存的作用域末尾覆盖此内存。因此,保证每个块仅在其各自的if - 语句部分中有效。代码将编译时没有错误,但在运行时可能正常或可能无法正常运行。如果它没有决定生成覆盖所选块的代码,代码将运行而不会出错,但如果确实如此,则肯定会发生崩溃。

我不明白“如果它没有决定生成覆盖所选块的代码,那么代码就可以正常运行,但如果确实如此,那肯定会发生崩溃。”

有人可以解释并举例说明吗?

2 个答案:

答案 0 :(得分:1)

问题类似于在函数本地创建的C数组,然后在函数返回后使用:

#import <Foundation/Foundation.h>

dispatch_block_t global_block;
int * global_arr;

void set_globals(void)
{
    if( YES ){
        global_block = ^{
            NSLog(@"Summer is butter on your chin and corn mush between every tooth.");
        };
        int arr[5] = {1, 2, 3, 4, 5};
        global_arr = arr;
    }
}

void write_on_the_stack(int i)
{
    int arr[5] = {64, 128, 256, 512, 1024};
    int v = arr[3];

    dispatch_block_t b = ^{
        int j = i + 10;
        j += v;
    };

    b();
}


int main(int argc, const char * argv[])
{

    @autoreleasepool {

        set_globals();

        write_on_the_stack();

        global_block();

        NSLog(@"%d", global_arr[0]);    // Prints garbage
    }
    return 0;
}

用于存储数组值的堆栈空间可以用于任何目的。我在这里使用单独的功能,因为它最可靠地证明了问题。对于您的确切情况,使用if块和相同函数中的访问,编译器仍可以自由地重用堆栈空间。 可能不,但你不能依赖它。你打破了语言的范围规则(从C派生)。

正如Jesse Rusak和CrimsonChris在评论中指出的那样,在ARC 下编译了一个块类型变量,Block就像数组一样在堆栈上创建,但是从堆栈中复制(到堆)当它存储在强指针中时。默认情况下,所有对象指针(包括全局对象)都很强大。

如果您没有使用ARC编译,这将是不可靠的。我无法用我当前的编译器提出一个失败的例子,但同样,它违反了规则,编译器没有义务做你想做的事。

答案 1 :(得分:-2)

基本上这就是说,如果代码在单独的线程上运行,并且某些内容被分配给当前使用但在块()之前的内存区域

void (^block)();
if (/* some condition *)) {
    block = ^ {
        NSLog(@"Block A");
    }
} else {
    block = ^ {
        NSLog(@"Block B");
    }
}
<--- another thread overwrites the **block** block
block(); <--- runtime error since **block** has been dereferenced.