Blocks aren't restricted to read-only access to the outer scope. You can qualify auto objects with "__block". Similar to the "register" qualifier, you're not allowed to take a pointer to the object; neither from the Block nor the outer function. But, this allows the Block to modify the value of the object.
It's just a small step, then, to allow full read-write access without requiring the programmer to qualify the storage type. The compiler could detect expressions--and does, I'm sure--which modify the object, and automagically change it's storage type. But they decided not to do this, I think, because they wanted the restrictions on the object to be explicit (and documented in code), and not as a side-effect of how the Block happens to handle the object.
Posted Sep 16, 2009 19:30 UTC (Wed) by mgedmin (subscriber, #34497)
[Link]
What happens when the variable you can only read is a pointer? Can you write to memory locations referenced from it, or will the compiler forbid that?
Tornado and Grand Central Dispatch: a quick look
Posted Sep 16, 2009 20:37 UTC (Wed) by wahern (subscriber, #37304)
[Link]
Do you mean this?
int i;
int *p = &i;
(void)(^block)(void) = ^{ *p = 0; }
I'm fairly certain (but you can read the documentation yourself), that such code is fine. I think you might be confusing (or rather, not distinguishing):
int *const p;
with
const int *p;
and/or
const int *const p;
The first is effectively what you get from the perspective of the Block's scope. Note also that, by default, the semantics of the Block's "closure" are copy-by-value, not by-reference. This is the reason for the read-only restriction. So that:
int i = 42;
(void)(^block)(void) = ^{ printf("%d", i); }
i = 7
printf("%d", i); // yields 7
block(); // yields 42
You'd get the expected result--both printing 7--with:
__block int i = 42;
But this is all explained, more-or-less, in Apple's documentation. What I'm still unclear of is how the run-time handles the collection of __block-storage objects. It uses some sort of reference counting on the Block objects, and the __block-storage objects are referenced by the Block, presumably. But how it manages to decrement the reference count, I'm not sure of. It might not do it at all, so that in order for a Block to remain valid outside of the scope it was defined in you have to use the API, i.e. Block_copy, etc.
Tornado and Grand Central Dispatch: a quick look
Posted Sep 16, 2009 22:05 UTC (Wed) by wahern (subscriber, #37304)
[Link]
I should clarify. The dereference of the const pointer, and assignment to the dereferenced non-const object is okay on its face, but like most everything else in C, it's your responsibility to make sure that the pointer points to a valid object. And in this case, it's critical to understand that the pointer value, not the pointer object, is what's being bound by the Block (unless, of course, you used the __block scope/storage qualifier). You can visualize the normal case by adding a pseudo-prologue:
int i;
int *p = &i;
(void)(^block)(void) = ^{ int *const local_p = p; *local_p = 0; };
// is the equivalent of ^{ *p = 0; }
Finally, I'm fairly certain now that in order to return a Block from a function you have to manually use Block_copy() and Block_release(). So, there's no magic garbage collection going on under the hood, though there is some magic in the optimization of moving a Block from auto storage to dynamic storage--thus the __block qualifier and rules.
Implication being, that if you are passed a Block object and aren't going to use it exclusively from the current scope (e.g. the code flow falls back into an event loop, storing the Block for later use), you need to manage it with Block_copy() and Block_release(), similar to malloc()/free(). And I'm sure libdispatch/GCD does exactly this when passed a Block.
Tornado and Grand Central Dispatch: a quick look
Posted Sep 17, 2009 8:54 UTC (Thu) by mgedmin (subscriber, #34497)
[Link]
Thank you for the answer.
Tornado and Grand Central Dispatch: a quick look
Posted Sep 18, 2009 10:39 UTC (Fri) by madcoder (subscriber, #30027)
[Link]
What I'm still unclear of is how the run-time handles the
collection of __block-storage objects. It uses some sort of reference
counting on the Block objects, and the __block-storage objects are
referenced by the Block, presumably. But how it manages to decrement the
reference count, I'm not sure of. It might not do it at all, so that in
order for a Block to remain valid outside of the scope it was defined in
you have to use the API, i.e. Block_copy, etc.
Well, there is a Block_release() function for that. And actually, wrt
__block variables, it stores a reference (pointer) to the variable on stack
until you do your first Block_copy, then (if that happens) it moves the
__block variable to the heap, alongside the copied block. IOW the __block
variable address may change between two runs of the same block, that's why
__block is incompatible with static.