9

I know that blocks are created in the stack. However, since I don't have enough knowledge about stack and local variables, I can not understand why I should move the block to heap in order to have expected result. Intuitively I feel like the block code chunk has only 1 instance in the stack, this code is referencing to local variable i 3 times. If I copy it to heap it will have 3 different instances and each time it will capture 3 different values of i during copy procedure. But I would really like to know more about block code in stack, heap and referencing local variables.

for (int i=0; i<3; i++)
    b[i] = ^{ return i;};
for (int i=0; i<3; i++)
    printf("b %d\n", b[i]());
Pablo
  • 28,133
  • 34
  • 125
  • 215
  • It looks like you got that snippet from bbum's [Block Tips and Tricks](http://www.friday.com/bbum/2009/08/29/blocks-tips-tricks/) article, and you've described what's happening pretty accurately. Could you be more specific about what pieces are missing for you? – jscs May 27 '11 at 05:07
  • You are right, I was just reading the article but it doesn't describe details. The missing part was first of all to confirm my understanding. Second is to understand how `code` fits to stack? I though only data can fit there. Can I make a copy of blocks in the stack itself, then I may not need to move it to heap? – Pablo May 27 '11 at 06:01

1 Answers1

13

Scopes, man. Scopes.

Rewrite that as:

void georgeClinton() {
    int (^b[3])(); // iirc
    // georgeClinton's scope
    for (int i=0; i<3; i++) {
        // for's scope
        b[i] = ^{ return i;};
    }
}

On every pass through that for() loop, for's scope is effectively a new scope. But, of course, scopes are on the stack.

When you call georgeClinton(), you effectively push georgeClinton()'s scope onto the stack. And when georgeClinton() returns with some funky goodness, georgeClinton()'s scope is popped off the stack, leaving the stack in whatever state it was in when the push happened (with a potential modification for the return value).

A for() loop is the same thing; each iteration pushes state onto the stack and pops it off at the end of the iteration.

Thus, if you store anything on the stack in an iteration of a for() loop, like a block, that thing will be destroyed at the end of the iteration. To preserve it, you must move it to the heap (where you control the lifespan of the state of any given allocation).

The key being that a block typed variable is really a pointer; it is a reference to a structure that defines the block. They start on the stack for efficiency and this can lead to subtle issues like this one.

Note that a block is really two things; it is a reference to the bit of immutable code that implements the block (which is really just like a function pointer) and it is a description of the data captured in the block and how that data is to be moved to the heap when copied.

That is, a block is the combination of data and code. Code that never changes. Data that is captured as the execution pointer passes over the expression that defines the block (i.e. the block "closes over" the current state of execution).

It is that latter bit that trips you up; when a block is created on the stack, it is created with slots to hold captured data, also on the stack.

bbum
  • 162,346
  • 23
  • 271
  • 359
  • 1
    The first line of `georgeClinton` should be `int (^b[3])();` since the blocks return a value. – Brigham May 27 '11 at 13:46
  • Thanks; fixed. SO needs a compiler for answers. :) – bbum May 27 '11 at 17:54
  • @bbum. I understand the reference to block code is residing on stack. Also that immutable code itself is residing on stack as well or anywhere else? When you say "they start on stack", do you mean the block structure starts on stack or the reference to block structure starts on stack? – Pablo Jun 03 '11 at 01:42
  • The code is never on the stack; code is code, immutable and men-mapped by dyld, never to change. The block structure itself starts on the stack. – bbum Jun 03 '11 at 05:25
  • 1
    @bbum: not sure if you will notice this comment, but I'm curious, if the block structure is destroyed 3 times, why b[] block array elements all contains the last block and its captured `i` state of `2`. I would expect all elements in array to be invalid pointers to stack, since they are all destroyed after the loop. – Pablo Jun 28 '11 at 01:19
  • 1
    It is merely coincidence; the stack isn't cleared when the scope of the for() loop is destroyed. The array doesn't contain the blocks, it contains references to the, now defunct, block slot on the stack in the for() loop. If you were to compile this with optimization (or use GC's clear stack func), you'd probably see different [undefined] behavior. – bbum Jun 28 '11 at 16:15
  • Don't the blocks created within the for loop need to be copied, e.g. with Block_copy? – Joey Adams Sep 16 '11 at 02:37
  • Yes -- it needs to be copied. The code, as written, is wrong, which was the point of the answer... see the discussion. – bbum Sep 16 '11 at 02:51