1

I found an block example in the book "Effective Objective-C 2.0"

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

The code is dangerous, and here is the explanation in the book:

The two blocks that are defined within the if and else statements are allocated within stack memory. When it allocates stack memory for each block, the compiler is free to overwrite this memory at the end of the scope in which that memory was allocated. So each block is guaranteed to be valid only within its respective if-statement section. The code would compile without error but at runtime may or may not function correctly. If it didn’t decide to produce code that overwrote the chosen block, the code would run without error, but if it did, a crash would certainly occur.

I don't understand the meaning of "If it didn’t decide to produce code that overwrote the chosen block, the code would run without error, but if it did, a crash would certainly occur."

Can someone explain and give examples?

jscs
  • 63,694
  • 13
  • 151
  • 195
lingguang1997
  • 451
  • 1
  • 4
  • 12
  • 1
    To be clear, the code you give is not dangerous when you're using ARC, at least if my reading of the [ARC block spec](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#blocks) is accurate, assigning to a local variable will copy the block to the heap, so there's no danger. – Jesse Rusak Jun 18 '14 at 18:31
  • The block is not a local variable. You can think of it as a function pointer which is declared out of any methods. – lingguang1997 Jun 18 '14 at 18:45
  • The block lives on the stack until it goes out of scope. Then it gets copied to the heap. This code looks fine to me, but you should always check if blocks are nil before running them just to be safe. – CrimsonChris Jun 18 '14 at 18:59
  • Check out this [question](http://stackoverflow.com/questions/24093802/objective-c-block-lifetime-arc). – CrimsonChris Jun 18 '14 at 19:00
  • @JesseRusak I think you're correct. Does the book say if this example is using ARC? – CrimsonChris Jun 18 '14 at 19:03
  • 1
    @lingguang1997 If `block` is global, it's still a strong store from the perspective of ARC and the same rules apply. – Jesse Rusak Jun 18 '14 at 19:59

2 Answers2

1

The issue is similar to that of a C array being created locally to a function and then used after the function returns:

#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;
}

The space on the stack that was used to store the values of the array may be re-used for any purpose. I use the separate function here because it most reliably demonstrates the problem. For your exact case, with the if block and the access in the same function, the compiler is still free to re-use the stack space. It may not, but you can't rely on that. You're breaking the scope rules of the language (derived from C).

As Jesse Rusak and CrimsonChris pointed out in comments, though, with a Block-type variable compiled under ARC, the Block is created on the stack like the array, but copied off the stack (to the heap) when it's stored in a strong pointer. All object pointers, including your global, are strong by default.

If you were not compiling with ARC, this would be unreliable. I can't come up with a failing example with my current compiler, but again, it's breaking the rules and the compiler is under no obligation to do what you want.

jscs
  • 63,694
  • 13
  • 151
  • 195
-2

Essentially what this is saying is that if there's code running on a separate thread, and something gets assigned to the area of memory currently used by block but before the block() call, then bad things will happen.

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.
Nick Coelius
  • 4,668
  • 3
  • 24
  • 30
  • In short, always check your blocks before running them. `if (block) block();` – CrimsonChris Jun 18 '14 at 18:57
  • 2
    This has nothing to do with threading; it has to do with scope and storage duration. It's exactly the same issue as returning a local array from a function: https://gist.github.com/woolsweater/884e476d46a9be59e077 The stack space can be reused by another scope. – jscs Jun 18 '14 at 19:57
  • 2
    Wait, what? I don't think this has anything to do with separate threads. If you're not under ARC, the block's storage may be gone (e.g. reused by some other local) by the time it's called, regardless of threading. Also, @CrimsonChris, checking for if(block) won't help because there's no guarantee the block will be nilled out. – Jesse Rusak Jun 18 '14 at 19:57
  • 2
    @CrimsonChris: that check won't help if the Block is overwritten by non-zero garbage. It'll evaluate to true and then crash. – jscs Jun 18 '14 at 19:58
  • @JoshCaswell You beat me to it :) – Jesse Rusak Jun 18 '14 at 19:58
  • Only by 5 seconds, @JesseRusak. :) – jscs Jun 18 '14 at 19:58
  • **If you are not under ARC** is an important caveat. Everything is simpler with ARC. :) – CrimsonChris Jun 18 '14 at 20:01
  • Checking `if (block)` is safe when the block is stored on the heap! – CrimsonChris Jun 18 '14 at 20:02
  • @CrimsonChris: ARC doesn't necessarily `nil` out strong pointers: https://gist.github.com/woolsweater/8a0ffda38904694bdaad – jscs Jun 18 '14 at 20:26
  • @JoshCaswell I never claimed that they did. `block` could be set to nil or possibly be a weak reference. Either way, checking `if (block)` before running is safe under ARC. – CrimsonChris Jun 18 '14 at 20:39
  • @Josh Caswell: Thank u for your comments. There block is assigned and get called immediately from the example, I don't think it is the same as returning a local array from a function – lingguang1997 Jun 19 '14 at 17:43
  • @Josh Caswell: The block is assigned and get called immediately, then everything is OK, right? but I am confused, if the block is declared globally, it won't gone; if it is a local variable, it cannot be called outside the scope, because no one can get the reference to it. Correct me if I am wrong. – lingguang1997 Jun 19 '14 at 17:51